diff --git a/.changeset/stepper-hover-text-popover.md b/.changeset/stepper-hover-text-popover.md new file mode 100644 index 0000000..96eedf3 --- /dev/null +++ b/.changeset/stepper-hover-text-popover.md @@ -0,0 +1,6 @@ +--- +"@sourceacademy/common-stepper": patch +"@sourceacademy/web-stepper": patch +--- + +Add a `hoverText` `SyntaxProfile` capability: a node type can now show a fixed-text hover popover (e.g. `built-in function print`) alongside its normal inline rendering, without collapsing/replacing that rendering the way `functionValues`'s mu-term does. Unlike a function value's popover (the node's own template, i.e. a body, rendered on demand), this shows a single already-formatted line the language stashed on the node ahead of time — there is no body to expand. A language opts in by listing the node type and the property holding that text in its `SyntaxProfile.hoverText`; `web-stepper` renders the popover generically from the rule, no per-language host code. diff --git a/src/common/stepper/src/index.ts b/src/common/stepper/src/index.ts index 63f1050..39d9440 100644 --- a/src/common/stepper/src/index.ts +++ b/src/common/stepper/src/index.ts @@ -162,6 +162,28 @@ export interface FunctionValueRule { nameProp: string; } +/** + * Declares a node type that shows a fixed hover popover alongside its normal inline rendering. + * + * Unlike {@link FunctionValueRule} — whose popover is the node's own template, i.e. a function's body, + * rendered on demand — this popover is a single line of *plain text* the language computed ahead of + * time and stashed on the node (e.g. `"built-in function print"` for a builtin referenced as a value, + * or `"module function stack"` for a name imported from a module): there is no body to expand, so + * nothing is collapsed or replaced — the node still renders exactly as `templates[type]` produces it, + * with a popover merely added on top. The host implements this generically from these rules, so any + * language gets the behaviour for any node type by listing it here — no per-language host code. + */ +export interface HoverTextRule { + /** The node `type` this applies to, e.g. `"Builtin"`. */ + type: string; + /** + * Dotted path to the node property holding the popover's already-formatted plain-text content (e.g. + * `"hoverText"`, or `"decl.hoverText"` for a node whose text lives on a child). When the path + * resolves to an empty value, no popover is added for that particular node. + */ + textProp: string; +} + /** * A language's complete rendering rules: a per-node-type template table plus the precedence maps the * host uses to insert parentheses generically. Authored once per language and shipped by its runner. @@ -178,6 +200,11 @@ export interface SyntaxProfile { * collapsed mu-term + hover popover instead of expanding its body inline. See {@link FunctionValueRule}. */ functionValues?: FunctionValueRule[]; + /** + * Node types that show a fixed-text hover popover alongside their normal inline rendering. See + * {@link HoverTextRule}. + */ + hoverText?: HoverTextRule[]; } /* -------------------------------------------------------------------------- */ diff --git a/src/web/stepper/src/SubstVisualizer.tsx b/src/web/stepper/src/SubstVisualizer.tsx index ea574fb..8b81e24 100644 --- a/src/web/stepper/src/SubstVisualizer.tsx +++ b/src/web/stepper/src/SubstVisualizer.tsx @@ -380,6 +380,23 @@ function ProfileFunctionDefinitionPopover({ ); } +/** + * The popover body for a {@link HoverTextRule}: a single already-formatted line the language + * computed ahead of time (e.g. `"built-in function print"`), unlike + * {@link ProfileFunctionDefinitionPopover}'s expanded function body — there is no body to render here, + * just the text, inside the same chrome the function-definition popover uses. + */ +function ProfileHoverTextPopover({ text }: { text: string }) { + return ( +