Add InlineAI conflict detection and styling to diff extension

This commit is contained in:
FBarrca 2024-10-27 22:32:05 +01:00
parent 5562c50620
commit 0b36cd27c8
2 changed files with 92 additions and 31 deletions

View file

@ -14,9 +14,11 @@ import { RangeSetBuilder } from "@codemirror/state";
*
* - Current Conflict: [- ... -]
* - Incoming Conflict: {+ ... +}
* - Selected for InlineAI: <<InlineAI<< ... >>InlineAI>>
*/
const CURRENT_CONFLICT_REGEX = /\[-[\s\S]*?-\]/g;
const INCOMING_CONFLICT_REGEX = /\{\+[\s\S]*?\+\}/g;
const CURRENT_CONFLICT_REGEX = /\[-([\s\S]*?)-\]/g;
const INCOMING_CONFLICT_REGEX = /\{\+([\s\S]*?)\+\}/g;
const INLINE_AI_CONFLICT_REGEX = /<<InlineAI<<([\s\S]*?)>>InlineAI>>/g; // Added regex for InlineAI
/**
* Define decorations with corresponding CSS classes.
@ -29,8 +31,20 @@ const incomingConflictDecoration = Decoration.mark({
class: "cm-conflict-incoming",
});
const selectedForInlineAIDecoration = Decoration.mark({
// Renamed for clarity
class: "cm-selected-inline",
});
// Decoration to hide conflict markers
const hideDecoration = Decoration.replace({
// This decoration replaces the matched text with nothing, effectively hiding it
// The `inclusive: false` ensures it doesn't affect surrounding text
inclusive: false,
});
/**
* ViewPlugin to detect and decorate conflict markers.
* ViewPlugin to detect and decorate conflict markers, including InlineAI markers.
*/
const conflictMarkersPlugin = ViewPlugin.fromClass(
class {
@ -50,53 +64,94 @@ const conflictMarkersPlugin = ViewPlugin.fromClass(
const builder = new RangeSetBuilder<Decoration>();
const text = view.state.doc.toString();
// Collect all matches for current and incoming conflicts
const conflicts: {
// Array to hold all decorations to be added
type DecorationEntry = {
from: number;
to: number;
decoration: Decoration;
}[] = [];
};
const decorationsToAdd: DecorationEntry[] = [];
let match: RegExpExecArray | null;
// Match Current Conflicts
while ((match = CURRENT_CONFLICT_REGEX.exec(text)) !== null) {
// Helper function to collect decorations
const collectConflict = (
match: RegExpExecArray,
prefixLength: number,
suffixLength: number,
decoration: Decoration
) => {
const start = match.index;
const end = match.index + match[0].length;
conflicts.push({
from: start,
to: end,
decoration: currentConflictDecoration,
const innerStart = start + prefixLength;
const innerEnd = end - suffixLength;
// Add decoration to highlight the inner content
decorationsToAdd.push({
from: innerStart,
to: innerEnd,
decoration,
});
// Add decorations to hide the prefix and suffix
decorationsToAdd.push({
from: start,
to: start + prefixLength,
decoration: hideDecoration,
});
decorationsToAdd.push({
from: end - suffixLength,
to: end,
decoration: hideDecoration,
});
};
// Match Current Conflicts
while ((match = CURRENT_CONFLICT_REGEX.exec(text)) !== null) {
collectConflict(match, 0, 0, currentConflictDecoration); // '[-' and '-]'
}
// Match Incoming Conflicts
while ((match = INCOMING_CONFLICT_REGEX.exec(text)) !== null) {
const start = match.index;
const end = match.index + match[0].length;
conflicts.push({
from: start,
to: end,
decoration: incomingConflictDecoration,
});
collectConflict(match, 0, 0, incomingConflictDecoration); // '{+' and '+}'
}
// Sort conflicts by start position
conflicts.sort((a, b) => a.from - b.from);
// Match InlineAI Conflicts
while ((match = INLINE_AI_CONFLICT_REGEX.exec(text)) !== null) {
// collectConflict(match, 12, 12, selectedForInlineAIDecoration); // '<<InlineAI<<' and '>>InlineAI>>'
collectConflict(match, 0, 0, selectedForInlineAIDecoration); // '<<InlineAI<<' and '>>InlineAI>>'
}
// Check for overlapping conflicts and handle accordingly
for (let i = 0; i < conflicts.length; i++) {
if (i > 0 && conflicts[i].from < conflicts[i - 1].to) {
// Sort all decorations by 'from' position and 'startSide'
decorationsToAdd.sort((a, b) => {
if (a.from !== b.from) {
return a.from - b.from;
}
// Optional: Define startSide if necessary. Codemirror's RangeSetBuilder handles this internally.
return 0;
});
// Check for overlapping decorations and handle accordingly
let lastAddedTo = -1;
for (const deco of decorationsToAdd) {
if (deco.from < lastAddedTo) {
console.warn(
"Overlapping conflict markers detected. Skipping overlapping decoration."
`Overlapping conflict markers detected between positions ${
deco.from
} and ${lastAddedTo}. Skipping overlapping decoration. Nearby text: "${text.slice(
deco.from - 10,
deco.to + 10
)} Tried to add decoration: ${
deco.decoration.spec.classList
}`
);
continue; // Skip overlapping decorations to prevent errors
}
builder.add(
conflicts[i].from,
conflicts[i].to,
conflicts[i].decoration
);
builder.add(deco.from, deco.to, deco.decoration);
if (deco.to > lastAddedTo) {
lastAddedTo = deco.to;
}
}
return builder.finish();

View file

@ -89,4 +89,10 @@ If your plugin does not need CSS, delete this file.
/* border-left: 4px solid green; */
padding-left: 2px;
}
.cm-selected-inline {
background-color: var(--text-highlight-bg);
}