mirror of
https://github.com/kunalja/VariantEditor.git
synced 2026-07-22 13:00:32 +00:00
fix highlighting to be per editor
This commit is contained in:
parent
0cab42971b
commit
d41d96f75a
1 changed files with 185 additions and 122 deletions
307
src/main.ts
307
src/main.ts
|
|
@ -34,21 +34,48 @@ class ClickableVariantWidget extends WidgetType {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Select the entire variant in the editor
|
||||
// Instead of using potentially stale positions, find the variant in the current document
|
||||
const state = view.state;
|
||||
const doc = state.doc;
|
||||
|
||||
// Find the positions in the current state
|
||||
const from = this.from;
|
||||
const to = this.to;
|
||||
|
||||
// Set the selection to the entire variant
|
||||
view.dispatch({
|
||||
selection: { anchor: from, head: to }
|
||||
});
|
||||
|
||||
// Call the highlightSelection method
|
||||
this.plugin.highlightSelection();
|
||||
const docText = doc.toString();
|
||||
|
||||
// Find the variant pattern in the document
|
||||
const variantRegex = new RegExp(this.fullVariant.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
|
||||
let match;
|
||||
let variantFound = false;
|
||||
|
||||
while ((match = variantRegex.exec(docText)) !== null) {
|
||||
const matchStart = match.index;
|
||||
const matchEnd = matchStart + match[0].length;
|
||||
|
||||
// Check if this match is around the expected position (within reasonable range)
|
||||
if (Math.abs(matchStart - this.from) < 100) { // Allow some drift
|
||||
// Set the selection to the entire variant
|
||||
view.dispatch({
|
||||
selection: { anchor: matchStart, head: matchEnd }
|
||||
});
|
||||
|
||||
variantFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!variantFound) {
|
||||
// Fallback: try to use original positions but validate they're reasonable
|
||||
if (this.from >= 0 && this.to <= doc.length && this.from < this.to) {
|
||||
view.dispatch({
|
||||
selection: { anchor: this.from, head: this.to }
|
||||
});
|
||||
variantFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (variantFound) {
|
||||
// Call the highlightSelection method
|
||||
this.plugin.highlightSelection();
|
||||
} else {
|
||||
console.warn('Could not find variant in document:', this.fullVariant);
|
||||
}
|
||||
});
|
||||
|
||||
return span;
|
||||
|
|
@ -62,17 +89,15 @@ class ClickableVariantWidget extends WidgetType {
|
|||
}
|
||||
|
||||
export default class VariantEditor extends Plugin {
|
||||
// Track active line for dimming
|
||||
private activeLine: number | null = null;
|
||||
// Store the extension to update it later
|
||||
private dimExtension: Extension | null = null;
|
||||
// Track previous cursor line
|
||||
private previousCursorLine: number | null = null;
|
||||
// Store the selected text for highlighting
|
||||
private selectedText: string | null = null;
|
||||
// Store the selection range
|
||||
private selectionFrom: number | null = null;
|
||||
private selectionTo: number | null = null;
|
||||
// Track active line for dimming - now per editor
|
||||
private activeEditorDimming = new Map<EditorView, {
|
||||
activeLine: number | null;
|
||||
selectedText: string | null;
|
||||
selectionFrom: number | null;
|
||||
selectionTo: number | null;
|
||||
previousCursorLine: number | null;
|
||||
dimExtension: Extension | null;
|
||||
}>();
|
||||
|
||||
async onload() {
|
||||
try {
|
||||
|
|
@ -101,12 +126,9 @@ export default class VariantEditor extends Plugin {
|
|||
editorCallback: (editor) => this.commitAllVariants(editor)
|
||||
});
|
||||
|
||||
// Register the editor extension for dimming
|
||||
this.dimExtension = this.createDimExtension();
|
||||
|
||||
// Register the editor extension for variant indicators
|
||||
// Register the editor extensions
|
||||
this.registerEditorExtension(this.createVariantIndicatorExtension());
|
||||
this.registerEditorExtension(this.dimExtension);
|
||||
this.registerEditorExtension(this.createGlobalDimExtension());
|
||||
} catch (e) {
|
||||
console.error('Error during initialization:', e);
|
||||
}
|
||||
|
|
@ -114,6 +136,8 @@ export default class VariantEditor extends Plugin {
|
|||
|
||||
onunload() {
|
||||
this.clearHighlight();
|
||||
// Clean up all editor-specific dimming
|
||||
this.activeEditorDimming.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -220,68 +244,89 @@ export default class VariantEditor extends Plugin {
|
|||
);
|
||||
}
|
||||
|
||||
private createDimExtension(): Extension {
|
||||
// Get reference to the plugin instance for the view plugin to use
|
||||
/**
|
||||
* Creates a global extension that only dims lines in the editor that has active variant editing
|
||||
*/
|
||||
private createGlobalDimExtension(): Extension {
|
||||
// Get reference to the plugin instance
|
||||
const pluginInstance = this;
|
||||
|
||||
// Create a state field to track cursor position changes
|
||||
const cursorTrackingField = StateField.define({
|
||||
const cursorTrackingField = StateField.define<{ previousCursorLine: number | null }>({
|
||||
create(state) {
|
||||
return null;
|
||||
return { previousCursorLine: null };
|
||||
},
|
||||
update(value, transaction) {
|
||||
if (transaction.selection) {
|
||||
const selection = transaction.newSelection.main;
|
||||
const currentLine = transaction.newDoc.lineAt(selection.head).number;
|
||||
|
||||
// Store the current cursor line
|
||||
if (pluginInstance.previousCursorLine === null) {
|
||||
pluginInstance.previousCursorLine = currentLine;
|
||||
}
|
||||
|
||||
// If we have an active line and cursor moved to a different line, clear dimming
|
||||
if (pluginInstance.activeLine !== null &&
|
||||
currentLine !== pluginInstance.activeLine &&
|
||||
currentLine !== pluginInstance.previousCursorLine) {
|
||||
// Schedule clearing on next tick to avoid update-during-update
|
||||
setTimeout(() => {
|
||||
pluginInstance.clearHighlight();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Update previous cursor line
|
||||
pluginInstance.previousCursorLine = currentLine;
|
||||
|
||||
// We'll handle cursor tracking in the view plugin where we have access to the EditorView
|
||||
return { previousCursorLine: currentLine };
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
// Create the view plugin for decorations
|
||||
// Create the view plugin for decorations that only applies to editors with active dimming
|
||||
const dimPlugin = ViewPlugin.fromClass(
|
||||
class implements PluginValue {
|
||||
decorations: DecorationSet;
|
||||
private lastCursorLine: number | null = null;
|
||||
private view: EditorView;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.view = view;
|
||||
this.decorations = this.buildDecorations(view);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
// Update decorations if needed
|
||||
if (update.docChanged || update.viewportChanged || pluginInstance.activeLine !== null) {
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
// Handle cursor movement for clearing dimming
|
||||
if (update.selectionSet) {
|
||||
const currentLine = update.state.doc.lineAt(update.state.selection.main.head).number;
|
||||
const editorState = pluginInstance.activeEditorDimming.get(update.view);
|
||||
|
||||
if (editorState && editorState.activeLine !== null) {
|
||||
// If cursor moved to a different line than the active line, clear dimming
|
||||
if (currentLine !== editorState.activeLine &&
|
||||
currentLine !== this.lastCursorLine) {
|
||||
setTimeout(() => {
|
||||
pluginInstance.clearHighlightForEditor(update.view);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastCursorLine = currentLine;
|
||||
}
|
||||
|
||||
// Always update decorations to check if this editor should have dimming
|
||||
this.decorations = this.buildDecorations(update.view);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Clean up editor state when the view is destroyed (e.g., tab closed)
|
||||
// We need to find which editor this view corresponds to and remove its state
|
||||
for (const [editorView, editorState] of pluginInstance.activeEditorDimming) {
|
||||
if (editorView === this.view) {
|
||||
pluginInstance.activeEditorDimming.delete(editorView);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildDecorations(view: EditorView): DecorationSet {
|
||||
// If no active line is set, return empty decorations
|
||||
if (pluginInstance.activeLine === null) {
|
||||
// Get editor-specific state - only apply dimming if this editor has active variant editing
|
||||
const editorState = pluginInstance.activeEditorDimming.get(view);
|
||||
|
||||
// If no editor state or no active line is set for this editor, return empty decorations
|
||||
if (!editorState || editorState.activeLine === null) {
|
||||
return Decoration.none;
|
||||
}
|
||||
|
||||
// Collect all decorations first, then sort and add them
|
||||
const allDecorations = [];
|
||||
const activeLine = pluginInstance.activeLine;
|
||||
|
||||
const activeLine = editorState.activeLine;
|
||||
|
||||
// First pass: Collect line decorations for all lines except the active one
|
||||
for (let i = 1; i <= view.state.doc.lines; i++) {
|
||||
if (i !== activeLine) {
|
||||
|
|
@ -301,12 +346,12 @@ export default class VariantEditor extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// Second pass: Collect highlight decoration only for the currently selected text
|
||||
if (pluginInstance.selectedText && activeLine <= view.state.doc.lines && pluginInstance.selectionFrom !== null && pluginInstance.selectionTo !== null) {
|
||||
// Second pass: Collect highlight decoration only for the currently selected text in this editor
|
||||
if (editorState.selectedText && activeLine <= view.state.doc.lines && editorState.selectionFrom !== null && editorState.selectionTo !== null) {
|
||||
try {
|
||||
// Use the exact selection positions instead of searching for all instances
|
||||
const start = pluginInstance.selectionFrom;
|
||||
const end = pluginInstance.selectionTo;
|
||||
const start = editorState.selectionFrom;
|
||||
const end = editorState.selectionTo;
|
||||
|
||||
allDecorations.push({
|
||||
from: start,
|
||||
|
|
@ -320,29 +365,22 @@ export default class VariantEditor extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
// Sort all decorations by from position
|
||||
// Sort decorations by position and build the decoration set
|
||||
allDecorations.sort((a, b) => a.from - b.from);
|
||||
|
||||
// Now add them to the builder in sorted order
|
||||
const builder = new RangeSetBuilder<Decoration>();
|
||||
for (const { from, to, decoration } of allDecorations) {
|
||||
try {
|
||||
builder.add(from, to, decoration);
|
||||
} catch (e) {
|
||||
console.error(`Error adding decoration at position ${from}:`, e);
|
||||
}
|
||||
builder.add(from, to, decoration);
|
||||
}
|
||||
|
||||
return builder.finish();
|
||||
}
|
||||
destroy() { }
|
||||
},
|
||||
{
|
||||
decorations: (instance) => instance.decorations
|
||||
decorations: v => v.decorations
|
||||
}
|
||||
);
|
||||
|
||||
// Return both extensions
|
||||
return [cursorTrackingField, dimPlugin];
|
||||
}
|
||||
|
||||
|
|
@ -375,8 +413,6 @@ export default class VariantEditor extends Plugin {
|
|||
let selectedText = editor.getRange(from, to);
|
||||
if (!selectedText) return;
|
||||
|
||||
this.selectedText = selectedText;
|
||||
|
||||
// Setup for variant editing
|
||||
let initialText = selectedText;
|
||||
let initialActiveIndex = 0;
|
||||
|
|
@ -430,32 +466,45 @@ export default class VariantEditor extends Plugin {
|
|||
new Notice('Editing existing variant');
|
||||
}
|
||||
|
||||
// Get or create editor-specific state
|
||||
const editorView = (view.editor as any).cm;
|
||||
if (!this.activeEditorDimming.has(editorView)) {
|
||||
this.activeEditorDimming.set(editorView, {
|
||||
activeLine: null,
|
||||
selectedText: null,
|
||||
selectionFrom: null,
|
||||
selectionTo: null,
|
||||
previousCursorLine: null,
|
||||
dimExtension: null
|
||||
});
|
||||
}
|
||||
|
||||
const editorState = this.activeEditorDimming.get(editorView)!;
|
||||
|
||||
// Set active line for dimming
|
||||
this.activeLine = from.line + 1;
|
||||
editorState.activeLine = from.line + 1;
|
||||
|
||||
// Store the selected text for highlighting - make sure we use the potentially updated selection
|
||||
this.selectedText = selectedText;
|
||||
editorState.selectedText = selectedText;
|
||||
|
||||
// Convert EditorPosition to absolute character positions for highlighting
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView) {
|
||||
// Get the editor view
|
||||
const editorView = (activeView.editor as any).cm;
|
||||
if (editorView) {
|
||||
try {
|
||||
// Convert line/ch positions to absolute character positions
|
||||
const fromPos = editorView.state.doc.line(from.line + 1).from + from.ch;
|
||||
const toPos = editorView.state.doc.line(to.line + 1).from + to.ch;
|
||||
if (editorView) {
|
||||
try {
|
||||
// Convert line/ch positions to absolute character positions
|
||||
const fromPos = editorView.state.doc.line(from.line + 1).from + from.ch;
|
||||
const toPos = editorView.state.doc.line(to.line + 1).from + to.ch;
|
||||
|
||||
// Store the exact selection range for highlighting
|
||||
this.selectionFrom = fromPos;
|
||||
this.selectionTo = toPos;
|
||||
} catch (e) {
|
||||
console.error('Error converting positions:', e);
|
||||
}
|
||||
// Store the exact selection range for highlighting
|
||||
editorState.selectionFrom = fromPos;
|
||||
editorState.selectionTo = toPos;
|
||||
} catch (e) {
|
||||
console.error('Error converting positions:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// The global dimming extension will automatically pick up this editor's state
|
||||
// No need to register per-editor extensions
|
||||
|
||||
// Force editor refresh to apply decorations
|
||||
this.app.workspace.updateOptions();
|
||||
|
||||
|
|
@ -471,14 +520,14 @@ export default class VariantEditor extends Plugin {
|
|||
if (modalClosed) {
|
||||
// Modal was closed without committing (via ESC key or clicking outside)
|
||||
// Clear the highlights
|
||||
this.clearHighlight();
|
||||
this.clearHighlightForEditor(editorView);
|
||||
} else if (commitVariant === true) {
|
||||
// Replace the variant with just the active variant text (commit action)
|
||||
if (variantText) {
|
||||
editor.replaceRange(variantText, updateFrom, updateTo);
|
||||
new Notice(`Committed variant: "${variantText}"`);
|
||||
// Clear highlights when committing
|
||||
this.clearHighlight();
|
||||
this.clearHighlightForEditor(editorView);
|
||||
}
|
||||
} else {
|
||||
// Create or update the variant syntax (normal variant creation/update)
|
||||
|
|
@ -496,25 +545,19 @@ export default class VariantEditor extends Plugin {
|
|||
to.ch = updateFrom.ch + variantSyntax.length;
|
||||
|
||||
// Update selection positions for highlighting
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView) {
|
||||
const editorView = (activeView.editor as any).cm;
|
||||
if (editorView) {
|
||||
try {
|
||||
// Convert updated line/ch positions to absolute character positions
|
||||
const fromPos = editorView.state.doc.line(updateFrom.line + 1).from + updateFrom.ch;
|
||||
const toPos = editorView.state.doc.line(updateFrom.line + 1).from + updateFrom.ch + variantSyntax.length;
|
||||
try {
|
||||
// Convert updated line/ch positions to absolute character positions
|
||||
const fromPos = editorView.state.doc.line(updateFrom.line + 1).from + updateFrom.ch;
|
||||
const toPos = editorView.state.doc.line(updateFrom.line + 1).from + updateFrom.ch + variantSyntax.length;
|
||||
|
||||
// Update the exact selection range for highlighting
|
||||
this.selectionFrom = fromPos;
|
||||
this.selectionTo = toPos;
|
||||
// Update the exact selection range for highlighting
|
||||
editorState.selectionFrom = fromPos;
|
||||
editorState.selectionTo = toPos;
|
||||
|
||||
// Force editor refresh to update decorations
|
||||
this.app.workspace.updateOptions();
|
||||
} catch (e) {
|
||||
console.error('Error updating selection positions:', e);
|
||||
}
|
||||
}
|
||||
// Force editor refresh to update decorations
|
||||
this.app.workspace.updateOptions();
|
||||
} catch (e) {
|
||||
console.error('Error updating selection positions:', e);
|
||||
}
|
||||
|
||||
const action = isExistingVariant ? 'Updated' : 'Created';
|
||||
|
|
@ -522,7 +565,7 @@ export default class VariantEditor extends Plugin {
|
|||
if (commitVariant) {
|
||||
new Notice(`${action} variant with ${variants.length} options (${variants[activeIdx]} active)`);
|
||||
// Only clear highlights when committing
|
||||
this.clearHighlight();
|
||||
this.clearHighlightForEditor(editorView);
|
||||
}
|
||||
// Don't clear highlights during live updates
|
||||
}
|
||||
|
|
@ -616,18 +659,38 @@ export default class VariantEditor extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear highlighting for a specific editor
|
||||
*/
|
||||
private clearHighlightForEditor(editorView: EditorView): void {
|
||||
try {
|
||||
// Get editor-specific state
|
||||
const editorState = this.activeEditorDimming.get(editorView);
|
||||
|
||||
if (editorState) {
|
||||
// Reset state for this editor
|
||||
editorState.activeLine = null;
|
||||
editorState.selectedText = null;
|
||||
editorState.selectionFrom = null;
|
||||
editorState.selectionTo = null;
|
||||
|
||||
// Force editor refresh to remove decorations
|
||||
this.app.workspace.updateOptions();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error in clearHighlightForEditor:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear highlighting for all editors (legacy method for compatibility)
|
||||
*/
|
||||
private clearHighlight(): void {
|
||||
try {
|
||||
// Reset state
|
||||
this.activeLine = null;
|
||||
this.previousCursorLine = null;
|
||||
this.selectedText = null;
|
||||
this.selectionFrom = null;
|
||||
this.selectionTo = null;
|
||||
|
||||
// Force editor refresh
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (view) hackToRerender(view);
|
||||
// Clear highlighting for all editors
|
||||
for (const [editorView, editorState] of this.activeEditorDimming) {
|
||||
this.clearHighlightForEditor(editorView);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error in clearHighlight:', e);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue