diff --git a/packages/codemirror/autocomplete.mjs b/packages/codemirror/autocomplete.mjs
index 05473b5e..c065c365 100644
--- a/packages/codemirror/autocomplete.mjs
+++ b/packages/codemirror/autocomplete.mjs
@@ -10,9 +10,9 @@ const getInnerText = (html) => {
return div.textContent || div.innerText || '';
};
-function Autocomplete({ doc }) {
+export function Autocomplete({ doc, label }) {
return h`
-
${getDocLabel(doc)}
+
${label || getDocLabel(doc)}
${doc.description}
${doc.params?.map(
@@ -50,12 +50,7 @@ const jsdocCompletions = jsdoc.docs
.map((doc) /*: Completion */ => ({
label: getDocLabel(doc),
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
- info: () => {
- const node = document.createElement('div');
- const ac = Autocomplete({ doc });
- node.appendChild(ac);
- return node;
- },
+ info: () => Autocomplete({ doc }),
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
}));
diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs
index 234e8e1a..6713ac6f 100644
--- a/packages/codemirror/codemirror.mjs
+++ b/packages/codemirror/codemirror.mjs
@@ -7,6 +7,7 @@ import { Compartment, EditorState, Prec } from '@codemirror/state';
import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view';
import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
import { isAutoCompletionEnabled } from './autocomplete.mjs';
+import { isTooltipEnabled } from './tooltip.mjs';
import { flash, isFlashEnabled } from './flash.mjs';
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
import { keybindings } from './keybindings.mjs';
@@ -19,6 +20,7 @@ const extensions = {
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
theme,
isAutoCompletionEnabled,
+ isTooltipEnabled,
isPatternHighlightingEnabled,
isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []),
isFlashEnabled,
diff --git a/packages/codemirror/tooltip.mjs b/packages/codemirror/tooltip.mjs
new file mode 100644
index 00000000..7b7a36db
--- /dev/null
+++ b/packages/codemirror/tooltip.mjs
@@ -0,0 +1,76 @@
+import { hoverTooltip } from '@codemirror/view';
+import jsdoc from '../../doc.json';
+import { Autocomplete } from './autocomplete.mjs';
+
+const getDocLabel = (doc) => doc.name || doc.longname;
+
+let ctrlDown = false;
+
+// Record Control key event to trigger or block the tooltip depending on the state
+window.addEventListener(
+ 'keyup',
+ function (e) {
+ if (e.key == 'Control') {
+ ctrlDown = false;
+ }
+ },
+ true,
+);
+
+window.addEventListener(
+ 'keydown',
+ function (e) {
+ if (e.key == 'Control') {
+ ctrlDown = true;
+ }
+ },
+ true,
+);
+
+export const strudelTooltip = hoverTooltip(
+ (view, pos, side) => {
+ // Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips
+ if (!ctrlDown) {
+ return null;
+ }
+ let { from, to, text } = view.state.doc.lineAt(pos);
+ let start = pos,
+ end = pos;
+ while (start > from && /\w/.test(text[start - from - 1])) {
+ start--;
+ }
+ while (end < to && /\w/.test(text[end - from])) {
+ end++;
+ }
+ if ((start == pos && side < 0) || (end == pos && side > 0)) {
+ return null;
+ }
+ let word = text.slice(start - from, end - from);
+ // Get entry from Strudel documentation
+ 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) {
+ return null;
+ }
+ }
+
+ return {
+ pos: start,
+ end,
+ above: false,
+ arrow: true,
+ create(view) {
+ let dom = document.createElement('div');
+ dom.className = 'strudel-tooltip';
+ const ac = Autocomplete({ doc: entry, label: word });
+ dom.appendChild(ac);
+ return { dom };
+ },
+ };
+ },
+ { hoverTime: 10 },
+);
+
+export const isTooltipEnabled = (on) => (on ? strudelTooltip : []);