Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(supersearch): Insert space before qualifier (LWS-286) #1198

Merged
merged 5 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 40 additions & 47 deletions packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import {
WidgetType,
type DecorationSet
} from '@codemirror/view';
import { EditorState, type Range } from '@codemirror/state';
import { EditorState, Range, RangeSet, RangeSetBuilder, RangeValue } from '@codemirror/state';
import { syntaxTree } from '@codemirror/language';
import { mount } from 'svelte';
import QualifierComponent from './QualifierComponent.svelte';
import insertQuotes from './insertQuotes.js';
import { messages } from '$lib/constants/messages.js';
import insertSpaceBeforeQualifier from './insertSpace.js';

export type Qualifier = {
key: string;
Expand All @@ -37,8 +38,7 @@ class QualifierWidget extends WidgetType {
readonly valueLabel: string | undefined,
readonly operator: string,
readonly operatorType: string | undefined,
readonly removeLink: string | undefined,
readonly atomic: boolean
readonly removeLink: string | undefined
) {
super();
}
Expand Down Expand Up @@ -72,8 +72,11 @@ class QualifierWidget extends WidgetType {
}

function lxlQualifierPlugin(getLabelFn?: GetLabelFunction) {
let atomicRangeSet: RangeSet<RangeValue> = RangeSet.empty;

function getQualifiers(view: EditorView) {
const widgets: Range<Decoration>[] = [];
const ranges = new RangeSetBuilder();
const doc = view.state.doc.toString();

for (const { from, to } of view.visibleRanges) {
Expand Down Expand Up @@ -106,19 +109,19 @@ function lxlQualifierPlugin(getLabelFn?: GetLabelFunction) {
valueLabel,
operator,
operatorType,
removeLink,
true // atomic
removeLink
)
});
const decorationRangeFrom = node.from;
const decorationRangeTo = valueLabel ? node.to : operatorNode?.to;

ranges.add(decorationRangeFrom, decorationRangeTo || node.to, qualifierDecoration);
widgets.push(qualifierDecoration.range(decorationRangeFrom, decorationRangeTo));
} else {
// Add invalid key mark decoration
const qualifierMark = Decoration.mark({
class: 'invalid',
inclusive: true,
atomic: false
inclusive: true
});
const invalidRangeFrom = keyNode ? keyNode.from : node.from;
const invalidRangeTo = keyNode ? keyNode.to : operatorNode?.from;
Expand All @@ -129,53 +132,43 @@ function lxlQualifierPlugin(getLabelFn?: GetLabelFunction) {
}
});
}
return Decoration.set(widgets, true); // true = sort
atomicRangeSet = ranges.finish();
return Decoration.set(widgets, true);
}

/**
* filter out non-atomics using custom property 'atomic'
*/
const filterAtomic = (from: number, to: number, decoration: Decoration) => {
return decoration.spec?.atomic || decoration.spec?.widget?.atomic;
};

const qualifierPlugin = ViewPlugin.fromClass(
class {
qualifiers: DecorationSet;
constructor(view: EditorView) {
this.qualifiers = getQualifiers(view);
}

update(update: ViewUpdate) {
if (update.docChanged || syntaxTree(update.startState) != syntaxTree(update.state)) {
// TODO: Calling getQualifiers on every document change is probably not good for performance
// Try optimizing; either run the function only on certain kinds of input, or split getQualifiers;
// one that updates the widgets (on input) and one that looks for labels (on data update)
this.qualifiers = getQualifiers(update.view);
} else {
for (const tr of update.transactions) {
for (const e of tr.effects) {
if (e.value.message === messages.NEW_DATA) {
this.qualifiers = getQualifiers(update.view);
}
class LxlQualifier {
qualifiers: DecorationSet;
constructor(view: EditorView) {
this.qualifiers = getQualifiers(view);
}
update(update: ViewUpdate) {
if (update.docChanged || syntaxTree(update.startState) != syntaxTree(update.state)) {
// TODO: Calling getQualifiers on every document change is probably not good for performance
// Try optimizing; either run the function only on certain kinds of input, or split getQualifiers;
// one that updates the widgets (on input) and one that looks for labels (on data update)
this.qualifiers = getQualifiers(update.view);
} else {
for (const tr of update.transactions) {
for (const e of tr.effects) {
if (e.value.message === messages.NEW_DATA) {
this.qualifiers = getQualifiers(update.view);
}
}
}
}
},
{
decorations: (instance) => instance.qualifiers,
eventHandlers: {},
provide: (plugin) => [
EditorView.atomicRanges.of((view) => {
const filteredRanges = view.plugin(plugin)?.qualifiers.update({ filter: filterAtomic });
return filteredRanges || Decoration.none;
}),
EditorState.transactionFilter.of(insertQuotes)
]
}
);
return qualifierPlugin;
}

const plugin = ViewPlugin.fromClass(LxlQualifier, {
decorations: (instance) => instance.qualifiers,
provide: () => [
EditorView.atomicRanges.of(() => atomicRangeSet),
EditorState.transactionFilter.of(insertQuotes),
insertSpaceBeforeQualifier(() => atomicRangeSet)
]
});

return plugin;
}

export default lxlQualifierPlugin;
jesperengstrom marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { EditorState, RangeSet, RangeValue } from '@codemirror/state';

const insertSpaceBeforeQualifier = (getRanges: () => RangeSet<RangeValue>) => {
return EditorState.transactionFilter.of((tr) => {
if (!tr.docChanged || (tr.isUserEvent('delete') && tr.state.selection.main.head === 0)) {
return tr;
} else {
let insert = {};
const atomicRanges = getRanges();
const oldCursorPos = tr.startState.selection.main.head;
const newCursorPos = tr.state.selection.main.head;

atomicRanges.between(oldCursorPos, oldCursorPos, () => {
insert = {
changes: {
from: newCursorPos,
to: newCursorPos,
insert: ' '
johanbissemattsson marked this conversation as resolved.
Show resolved Hide resolved
},
sequential: true,
selection: { anchor: newCursorPos }
};
return false;
});
return [tr, insert];
}
});
};

export default insertSpaceBeforeQualifier;
Loading