refactor: modularize autocomplete context handlers using strategy array
This commit is contained in:
parent
d87ce960d2
commit
0b67473bd3
1 changed files with 38 additions and 30 deletions
|
|
@ -114,23 +114,18 @@ const jsdocCompletions = jsdoc.docs
|
||||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const strudelAutocomplete = (context) => {
|
|
||||||
// --- Pitch names for scale key completion ---
|
// --- Handler functions for each context ---
|
||||||
// Ideally, these should be imported from packages/tonal/tonleiter.mjs
|
const pitchNames = [
|
||||||
// but are duplicated here as they are not exported.
|
'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'Fb', 'F', 'F#', 'Gb',
|
||||||
const pitchNames = [
|
'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B', 'Cb'
|
||||||
'C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'Fb', 'F', 'F#', 'Gb',
|
];
|
||||||
'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B', 'Cb'
|
|
||||||
];
|
function scalePreColonHandler(context) {
|
||||||
|
|
||||||
// Block fallback completions inside .scale("..."), even before the colon
|
|
||||||
let scalePreColonContext = context.matchBefore(/\.scale\(\s*['"][^'"]*$/);
|
let scalePreColonContext = context.matchBefore(/\.scale\(\s*['"][^'"]*$/);
|
||||||
if (scalePreColonContext) {
|
if (scalePreColonContext) {
|
||||||
// Only yield completions if a colon is present (handled below)
|
|
||||||
if (!scalePreColonContext.text.includes(':')) {
|
if (!scalePreColonContext.text.includes(':')) {
|
||||||
if (context.explicit) {
|
if (context.explicit) {
|
||||||
// Provide pitch completions on explicit request
|
|
||||||
// (Union of sharps and flats from tonleiter.mjs)
|
|
||||||
const text = scalePreColonContext.text;
|
const text = scalePreColonContext.text;
|
||||||
const match = text.match(/([A-Ga-g][#b]*)?$/);
|
const match = text.match(/([A-Ga-g][#b]*)?$/);
|
||||||
const fragment = match ? match[0] : '';
|
const fragment = match ? match[0] : '';
|
||||||
|
|
@ -139,19 +134,17 @@ export const strudelAutocomplete = (context) => {
|
||||||
const options = filtered.map((p) => ({ label: p, type: 'pitch' }));
|
const options = filtered.map((p) => ({ label: p, type: 'pitch' }));
|
||||||
return { from, options };
|
return { from, options };
|
||||||
} else {
|
} else {
|
||||||
// Block fallback completions
|
|
||||||
return { from: scalePreColonContext.to, options: [] };
|
return { from: scalePreColonContext.to, options: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (second block removed, handled above)
|
|
||||||
if (!scalePreColonContext.text.includes(':')) {
|
if (!scalePreColonContext.text.includes(':')) {
|
||||||
return { from: scalePreColonContext.to, options: [] };
|
return { from: scalePreColonContext.to, options: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
// 1. Check for sound context: s("..."), sound("...")
|
}
|
||||||
// Trigger after at least one letter is typed after whitespace or bracket inside the quotes
|
|
||||||
|
function soundHandler(context) {
|
||||||
let soundContext = context.matchBefore(/(s|sound)\(\s*['"][^'"]*$/);
|
let soundContext = context.matchBefore(/(s|sound)\(\s*['"][^'"]*$/);
|
||||||
if (soundContext) {
|
if (soundContext) {
|
||||||
const text = soundContext.text;
|
const text = soundContext.text;
|
||||||
|
|
@ -170,13 +163,13 @@ export const strudelAutocomplete = (context) => {
|
||||||
options,
|
options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bankHandler(context) {
|
||||||
let bankMatch = context.matchBefore(/bank\(\s*(['"])?([\w]*)$/);
|
let bankMatch = context.matchBefore(/bank\(\s*(['"])?([\w]*)$/);
|
||||||
if (bankMatch) {
|
if (bankMatch) {
|
||||||
let banks = bankCompletions();
|
let banks = bankCompletions();
|
||||||
console.log('[autocomplete] Bank context detected:', bankMatch.text);
|
|
||||||
console.log('[autocomplete] soundMap keys:', Object.keys(soundMap.get()));
|
|
||||||
console.log('[autocomplete] bankCompletions:', banks);
|
|
||||||
// Extract quote and fragment using regex groups on match.text
|
// Extract quote and fragment using regex groups on match.text
|
||||||
const groups = bankMatch.text.match(/(['"])?([\w]*)$/);
|
const groups = bankMatch.text.match(/(['"])?([\w]*)$/);
|
||||||
const quote = groups ? groups[1] : undefined;
|
const quote = groups ? groups[1] : undefined;
|
||||||
|
|
@ -205,19 +198,17 @@ export const strudelAutocomplete = (context) => {
|
||||||
options,
|
options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Check for scale context: .scale("..."), only after colon
|
function scaleAfterColonHandler(context) {
|
||||||
let scaleContext = context.matchBefore(/\.scale\(\s*['"][^'"]*:[^'"]*$/);
|
let scaleContext = context.matchBefore(/\.scale\(\s*['"][^'"]*:[^'"]*$/);
|
||||||
if (scaleContext) {
|
if (scaleContext) {
|
||||||
// Find the last colon in the text
|
|
||||||
const text = scaleContext.text;
|
const text = scaleContext.text;
|
||||||
const colonIdx = text.lastIndexOf(':');
|
const colonIdx = text.lastIndexOf(':');
|
||||||
if (colonIdx === -1) return null;
|
if (colonIdx === -1) return null;
|
||||||
// Get the fragment after the colon
|
|
||||||
const fragment = text.slice(colonIdx + 1);
|
const fragment = text.slice(colonIdx + 1);
|
||||||
// Filter scale completions by fragment
|
|
||||||
const filteredScales = scaleCompletions.filter((s) => s.label.startsWith(fragment));
|
const filteredScales = scaleCompletions.filter((s) => s.label.startsWith(fragment));
|
||||||
// Insert colon-form (replace spaces with colons) for completions
|
|
||||||
const options = filteredScales.map((s) => ({
|
const options = filteredScales.map((s) => ({
|
||||||
...s,
|
...s,
|
||||||
apply: s.label.replace(/\s+/g, ':')
|
apply: s.label.replace(/\s+/g, ':')
|
||||||
|
|
@ -228,19 +219,36 @@ export const strudelAutocomplete = (context) => {
|
||||||
options,
|
options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// fallback: original logic
|
function fallbackHandler(context) {
|
||||||
const word = context.matchBefore(/\w*/);
|
const word = context.matchBefore(/\w*/);
|
||||||
if (word && word.from === word.to && !context.explicit) return null;
|
if (word && word.from === word.to && !context.explicit) return null;
|
||||||
|
|
||||||
if (word) {
|
if (word) {
|
||||||
console.log('[autocomplete] Default context:', word.text);
|
|
||||||
return {
|
return {
|
||||||
from: word.from,
|
from: word.from,
|
||||||
options: jsdocCompletions,
|
options: jsdocCompletions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = [
|
||||||
|
scalePreColonHandler,
|
||||||
|
soundHandler,
|
||||||
|
bankHandler,
|
||||||
|
scaleAfterColonHandler,
|
||||||
|
// this handler *must* be last
|
||||||
|
fallbackHandler
|
||||||
|
];
|
||||||
|
|
||||||
|
export const strudelAutocomplete = (context) => {
|
||||||
|
for (const handler of handlers) {
|
||||||
|
const result = handler(context);
|
||||||
|
if (result) return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isAutoCompletionEnabled = (enabled) =>
|
export const isAutoCompletionEnabled = (enabled) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue