From 709b654ed39927f1c5f5ea8ae7bbde54f22e0f11 Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 13 Sep 2025 16:21:09 -0500 Subject: [PATCH] Add synonyms to autocomplete --- packages/codemirror/autocomplete.mjs | 26 ++++++++++++++++--- packages/codemirror/tooltip.mjs | 9 ++++--- packages/core/pattern.mjs | 12 +++++---- website/src/repl/Repl.css | 7 +++++ .../src/repl/components/panel/Reference.jsx | 3 +-- 5 files changed, 42 insertions(+), 15 deletions(-) diff --git a/packages/codemirror/autocomplete.mjs b/packages/codemirror/autocomplete.mjs index b56cab826..69aa3bc59 100644 --- a/packages/codemirror/autocomplete.mjs +++ b/packages/codemirror/autocomplete.mjs @@ -54,11 +54,12 @@ const buildExamples = (examples) => ` : ''; -export const Autocomplete = ({ doc, label }) => +export const Autocomplete = (doc) => h`
-

${label || getDocLabel(doc)}

+

${getDocLabel(doc)}

+ ${doc.synonyms_text ? `
Synonyms: ${doc.synonyms_text}
` : ''} ${doc.description ? `
${doc.description}
` : ''} ${buildParamsList(doc.params)} ${buildExamples(doc.examples)} @@ -74,19 +75,36 @@ const isValidDoc = (doc) => { const hasExcludedTags = (doc) => ['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)); +export const getSynonymDoc = (doc, synonym) => { + const synonyms = doc.synonyms || []; + const docLabel = getDocLabel(doc); + // Swap `doc.name` in for `s` in the list of synonyms + const synonymsWithDoc = [docLabel, ...synonyms].filter((x) => x && x !== synonym); + return { + ...doc, + name: synonym, + longname: synonym, + synonyms: synonymsWithDoc, + synonyms_text: synonymsWithDoc.join(', '), + }; +}; + const jsdocCompletions = (() => { const seen = new Set(); // avoid repetition const completions = []; for (const doc of jsdoc.docs) { if (!isValidDoc(doc) || hasExcludedTags(doc)) continue; - let labels = [getDocLabel(doc), ...(doc.synonyms || [])]; + const docLabel = getDocLabel(doc); + // Remove duplicates + const synonyms = doc.synonyms || []; + let labels = [docLabel, ...synonyms]; for (const label of labels) { // https://codemirror.net/docs/ref/#autocomplete.Completion if (label && !seen.has(label)) { seen.add(label); completions.push({ label, - info: () => Autocomplete({ doc, label }), + info: () => Autocomplete(getSynonymDoc(doc, label)), type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type }); } diff --git a/packages/codemirror/tooltip.mjs b/packages/codemirror/tooltip.mjs index f67e6d14a..d1d0479b2 100644 --- a/packages/codemirror/tooltip.mjs +++ b/packages/codemirror/tooltip.mjs @@ -1,6 +1,6 @@ import { hoverTooltip } from '@codemirror/view'; import jsdoc from '../../doc.json'; -import { Autocomplete } from './autocomplete.mjs'; +import { Autocomplete, getSynonymDoc } from './autocomplete.mjs'; const getDocLabel = (doc) => doc.name || doc.longname; @@ -52,10 +52,11 @@ export const strudelTooltip = hoverTooltip( let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0]; if (!entry) { // Try for synonyms - entry = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0]; - if (!entry) { + const doc = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0]; + if (!doc) { return null; } + entry = getSynonymDoc(doc, word); } return { @@ -66,7 +67,7 @@ export const strudelTooltip = hoverTooltip( create(view) { let dom = document.createElement('div'); dom.className = 'strudel-tooltip'; - const ac = Autocomplete({ doc: entry, label: word }); + const ac = Autocomplete(entry); dom.appendChild(ac); return { dom }; }, diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index addc301d5..39790f8a0 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -1246,7 +1246,8 @@ export const silence = gap(1); /* Like silence, but with a 'steps' (relative duration) of 0 */ export const nothing = gap(0); -/** A discrete value that repeats once per cycle. +/** + * A discrete value that repeats once per cycle. * * @returns {Pattern} * @example @@ -1299,7 +1300,8 @@ export function sequenceP(pats) { return result; } -/** The given items are played at the same time at the same length. +/** + * The given items are played at the same time at the same length. * * @return {Pattern} * @synonyms polyrhythm, pr @@ -1382,11 +1384,11 @@ export function stackBy(by, ...pats) { .setSteps(steps); } -/** Concatenation: combines a list of patterns, switching between them successively, one per cycle: - * - * synonyms: `cat` +/** + * Concatenation: combines a list of patterns, switching between them successively, one per cycle. * * @return {Pattern} + * @synonyms cat * @example * slowcat("e5", "b4", ["d5", "c5"]) * diff --git a/website/src/repl/Repl.css b/website/src/repl/Repl.css index 498679090..9cf51ff85 100644 --- a/website/src/repl/Repl.css +++ b/website/src/repl/Repl.css @@ -112,6 +112,13 @@ margin: 0 0 8px 0; } +.autocomplete-info-function-synonyms { + margin: 0 0 12px 0; + color: var(--foreground); + line-height: 1.5; + opacity: 0.8; +} + .autocomplete-info-function-description { margin: 0 0 12px 0; color: var(--foreground); diff --git a/website/src/repl/components/panel/Reference.jsx b/website/src/repl/components/panel/Reference.jsx index 81826aca2..6007667fa 100644 --- a/website/src/repl/components/panel/Reference.jsx +++ b/website/src/repl/components/panel/Reference.jsx @@ -16,8 +16,7 @@ const availableFunctions = (() => { if (!s || seen.has(s)) continue; seen.add(s); // Swap `doc.name` in for `s` in the list of synonyms - const notS = synonyms.filter((x) => x && x !== s); - const synonymsWithDoc = Array.from(new Set([doc.name, ...notS])); + const synonymsWithDoc = [doc.name, ...synonyms].filter((x) => x && x !== s); functions.push({ ...doc, name: s, // update names for the synonym