Skip to content

Commit

Permalink
Merge pull request #2990 from quantified-uncertainty/imports-tooltips
Browse files Browse the repository at this point in the history
Imports tooltips
  • Loading branch information
OAGr authored Jan 20, 2024
2 parents 264a591 + 440b7f8 commit a91d0f8
Show file tree
Hide file tree
Showing 17 changed files with 159 additions and 54 deletions.
5 changes: 5 additions & 0 deletions .changeset/grumpy-owls-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@quri/squiggle-components": patch
---

Tooltips for import strings can be injected with `renderImportTooltip` prop
6 changes: 5 additions & 1 deletion packages/components/src/components/CodeEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { forwardRef, useImperativeHandle } from "react";
import { forwardRef, ReactNode, useImperativeHandle } from "react";

import { SqError, SqProject, SqValuePath } from "@quri/squiggle-lang";

Expand All @@ -18,6 +18,10 @@ export type CodeEditorProps = {
sourceId: string;
fontSize?: number;
project: SqProject;
renderImportTooltip?: (params: {
project: SqProject;
importId: string;
}) => ReactNode;
};

export type CodeEditorHandle = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export function useSquiggleEditorExtensions(
const tooltipsExtension = useTooltipsExtension(view, {
project: params.project,
sourceId: params.sourceId,
renderImportTooltip: params.renderImportTooltip,
});

const highPrioritySquiggleExtensions = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { syntaxTree } from "@codemirror/language";
import { EditorView, hoverTooltip, repositionTooltips } from "@codemirror/view";
import { SyntaxNode } from "@lezer/common";
import { FC, PropsWithChildren, useEffect } from "react";
import { FC, PropsWithChildren, ReactNode, useEffect } from "react";

import {
getFunctionDocumentation,
SqProject,
SqValue,
} from "@quri/squiggle-lang";
import { getFunctionDocumentation, SqValue } from "@quri/squiggle-lang";

import { valueHasContext } from "../../lib/utility.js";
import { SquiggleValueChart } from "../SquiggleViewer/SquiggleValueChart.js";
Expand All @@ -17,6 +13,7 @@ import {
} from "../SquiggleViewer/ViewerProvider.js";
import { FnDocumentation } from "../ui/FnDocumentation.js";
import { useReactiveExtension } from "./codemirrorHooks.js";
import { CodeEditorProps } from "./index.js";
import { reactAsDom } from "./utils.js";

type Hover = NonNullable<ReturnType<typeof getFunctionDocumentation>>;
Expand Down Expand Up @@ -73,15 +70,22 @@ const HoverTooltip: FC<{ hover: Hover; view: EditorView }> = ({
</TooltipBox>
);

function nodeTooltip(syntaxNode: SyntaxNode, reactNode: ReactNode) {
return {
pos: syntaxNode.from,
end: syntaxNode.to,
above: true,
create: () => reactAsDom(reactNode),
};
}

// Based on https://codemirror.net/examples/tooltip/#hover-tooltips
// See also: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover
function buildWordHoverExtension({
function buildTooltipExtension({
project,
sourceId,
}: {
project: SqProject;
sourceId: string;
}) {
renderImportTooltip,
}: Pick<CodeEditorProps, "project" | "sourceId" | "renderImportTooltip">) {
return hoverTooltip((view, pos, side) => {
const { doc } = view.state;

Expand All @@ -96,27 +100,27 @@ function buildWordHoverExtension({
return null;
}

return {
pos: node.from,
end: node.to,
above: true,
create: () => reactAsDom(<HoverTooltip hover={hover} view={view} />),
};
};

const createTopLevelVariableNameTooltip = (
node: SyntaxNode,
value: SqValue
) => {
return {
pos: node.from,
end: node.to,
above: true,
create: () => reactAsDom(<ValueTooltip value={value} view={view} />),
};
return nodeTooltip(node, <HoverTooltip hover={hover} view={view} />);
};

switch (cursor.name) {
case "String": {
// Is this an import?
const stringNode = cursor.node;
if (!cursor.parent()) {
return null;
}
if (cursor.type.is("Import") && renderImportTooltip) {
const importId = getText(stringNode).slice(1, -1);
return nodeTooltip(
stringNode,
<TooltipBox view={view}>
{renderImportTooltip({ project, importId })}
</TooltipBox>
);
}
break;
}
case "Identifier":
if (getText(cursor.node).match(/^[A-Z]/)) {
// TODO - expand the namespace to the identifier, or just show the namespace documentation
Expand Down Expand Up @@ -170,7 +174,7 @@ function buildWordHoverExtension({
valueAst.variable.location.start.offset === node.from &&
valueAst.variable.location.end.offset === node.to
) {
return createTopLevelVariableNameTooltip(node, value);
return nodeTooltip(node, <ValueTooltip value={value} view={view} />);
}
}
}
Expand All @@ -194,14 +198,15 @@ export function useTooltipsExtension(
{
project,
sourceId,
}: {
project: SqProject;
sourceId: string;
}
renderImportTooltip,
}: Pick<CodeEditorProps, "project" | "sourceId" | "renderImportTooltip">
) {
return useReactiveExtension(
view,
() => [buildWordHoverExtension({ project, sourceId }), tooltipTheme],
[project, sourceId]
() => [
buildTooltipExtension({ project, sourceId, renderImportTooltip }),
tooltipTheme,
],
[project, sourceId, renderImportTooltip]
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useRef,
} from "react";

import { SqProject, SqValuePath } from "@quri/squiggle-lang";
import { SqProject } from "@quri/squiggle-lang";
import {
AdjustmentsVerticalIcon,
Bars3CenterLeftIcon,
Expand All @@ -25,7 +25,11 @@ import {
useUncontrolledCode,
} from "../../../lib/hooks/index.js";
import { altKey, getErrors } from "../../../lib/utility.js";
import { CodeEditor, CodeEditorHandle } from "../../CodeEditor/index.js";
import {
CodeEditor,
CodeEditorHandle,
CodeEditorProps,
} from "../../CodeEditor/index.js";
import { PlaygroundSettings } from "../../PlaygroundSettings.js";
import { PanelWithToolbar } from "../../ui/PanelWithToolbar/index.js";
import { ToolbarItem } from "../../ui/PanelWithToolbar/ToolbarItem.js";
Expand Down Expand Up @@ -54,8 +58,7 @@ type Props = {
/* Allows to inject extra items to the left panel's dropdown menu. */
renderExtraDropdownItems?: RenderExtraControls;
renderExtraModal?: Parameters<typeof PanelWithToolbar>[0]["renderModal"];
onViewValuePath?: (path: SqValuePath) => void;
};
} & Pick<CodeEditorProps, "onViewValuePath" | "renderImportTooltip">;

// for interactions with this component from outside
export type LeftPlaygroundPanelHandle = {
Expand Down Expand Up @@ -164,8 +167,9 @@ export const LeftPlaygroundPanel = forwardRef<LeftPlaygroundPanelHandle, Props>(
showGutter={true}
lineWrapping={props.settings.editorSettings.lineWrapping}
onChange={setCode}
onViewValuePath={props.onViewValuePath}
onSubmit={runnerState.run}
onViewValuePath={props.onViewValuePath}
renderImportTooltip={props.renderImportTooltip}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ export type SquigglePlaygroundProps = {
height?: CSSProperties["height"];
} & Pick<
Parameters<typeof LeftPlaygroundPanel>[0],
"renderExtraControls" | "renderExtraDropdownItems" | "renderExtraModal"
| "renderExtraControls"
| "renderExtraDropdownItems"
| "renderExtraModal"
| "renderImportTooltip"
> &
PartialPlaygroundSettings;

Expand All @@ -77,6 +80,7 @@ export const SquigglePlayground: React.FC<SquigglePlaygroundProps> = (
renderExtraControls,
renderExtraDropdownItems,
renderExtraModal,
renderImportTooltip,
height = 500,
sourceId,
...defaultSettings
Expand Down Expand Up @@ -155,6 +159,7 @@ export const SquigglePlayground: React.FC<SquigglePlaygroundProps> = (
renderExtraDropdownItems={renderExtraDropdownItems}
renderExtraModal={renderExtraModal}
onViewValuePath={(path) => rightPanelRef.current?.viewValuePath(path)}
renderImportTooltip={renderImportTooltip}
ref={leftPanelRef}
/>
);
Expand Down
6 changes: 3 additions & 3 deletions packages/hub/src/app/RootLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { isModelRoute, isModelSubroute } from "@/routes";

import { PageFooter } from "../components/layout/RootLayout/PageFooter";
import { PageMenu } from "../components/layout/RootLayout/PageMenu";
import { ClientApp } from "./ClientApp";
import { ReactRoot } from "../components/ReactRoot";

import { RootLayoutQuery } from "@/__generated__/RootLayoutQuery.graphql";

Expand Down Expand Up @@ -53,8 +53,8 @@ export const RootLayout: FC<
}>
> = ({ session, children }) => {
return (
<ClientApp session={session}>
<ReactRoot session={session}>
<InnerRootLayout>{children}</InnerRootLayout>
</ClientApp>
</ReactRoot>
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useSession } from "next-auth/react";
import { BaseSyntheticEvent, FC, useMemo, useState } from "react";
import { FormProvider, useFieldArray, useForm } from "react-hook-form";
import { graphql, useFragment } from "react-relay";
Expand All @@ -22,13 +23,16 @@ import {
VersionedSquigglePlayground,
versionSupportsDropdownMenu,
versionSupportsExports,
versionSupportsImportTooltip,
} from "@quri/versioned-squiggle-components";

import { EditModelExports } from "@/components/exports/EditModelExports";
import { ReactRoot } from "@/components/ReactRoot";
import { FormModal } from "@/components/ui/FormModal";
import { useAvailableHeight } from "@/hooks/useAvailableHeight";
import { useMutationForm } from "@/hooks/useMutationForm";
import { extractFromGraphqlErrorUnion } from "@/lib/graphqlHelpers";
import { ImportTooltip } from "@/squiggle/components/ImportTooltip";
import {
serializeSourceId,
squiggleHubLinker,
Expand Down Expand Up @@ -122,6 +126,8 @@ export const EditSquiggleSnippetModel: FC<Props> = ({
modelRef,
forceVersionPicker,
}) => {
const { data: session } = useSession();

const model = useFragment(
graphql`
fragment EditSquiggleSnippetModel on Model {
Expand Down Expand Up @@ -375,6 +381,14 @@ export const EditSquiggleSnippetModel: FC<Props> = ({
};
}

if (versionSupportsImportTooltip.props(playgroundProps)) {
playgroundProps.renderImportTooltip = ({ importId }) => (
<ReactRoot session={session}>
<ImportTooltip importId={importId} />
</ReactRoot>
);
}

return (
<FormProvider {...form}>
<form onSubmit={() => onSubmit()}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const VersionedSquiggleModelExportPage: FC<
const project = new squiggleLang.SqProject({
linker: squiggleHubLinker,
});

const rootPath = new squiggleLang.SqValuePath({
root: "bindings",
items: [{ type: "string", value: variableName }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const RelativeValuesModelLayout: FC<
<div className="mb-6 py-2 px-2 flex justify-between">
<div className="flex items-center">
<ScaleIcon className="text-gray-700 mr-2 opacity-40" size={22} />
<div className="flex text-md font-mono font-bold text-gray-700">
<div className="flex font-mono font-bold text-gray-700">
{variableName}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/hub/src/components/EntityCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const EntityCard: FC<Props> = ({
<div className="w-full">
<Link className="group" href={href}>
<div className="flex items-center gap-1 mb-1">
<div className="text-md font-semibold text-blue-500 group-hover:underline">
<div className="font-semibold text-blue-500 group-hover:underline">
{showOwner ? ownerName + "/" : ""}
{slug}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { WithToasts } from "@quri/ui";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import { getCurrentEnvironment } from "@/relay/environment";

export const ClientApp: FC<PropsWithChildren<{ session: Session | null }>> = ({
// This component is used in the app's root layout to configure all common providers and wrappers.
// It's also useful when you want to mount a separate React root. One example is CodeMirror tooltips, which are mounted as separate DOM elements.
export const ReactRoot: FC<PropsWithChildren<{ session: Session | null }>> = ({
session,
children,
}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const TableRow: React.FC<TableRowProps> = ({ label, number }) => (
<div className="text-slate-400 py-1 mt-1 font-normal text-left text-xs col-span-1">
{label}
</div>
<div className="py-1 pl-2 text-left text-slate-600 text-md col-span-2">
<div className="py-1 pl-2 text-left text-slate-600 col-span-2">
<NumberShower number={number} precision={2} />
</div>
</Fragment>
Expand Down Expand Up @@ -124,9 +124,7 @@ export const ItemSideBar: FC<Props> = ({
return (
<div>
<div className="mt-2 mb-6 flex overflow-x-auto items-center p-1">
<span className="text-slate-500 text-md whitespace-nowrap mr-1">
value
</span>
<span className="text-slate-500 whitespace-nowrap mr-1">value</span>
<span className="text-slate-300 text-xl whitespace-nowrap">(</span>
<span className="text-sm bg-slate-200 font-semibold bg-opacity-80 rounded-sm text-slate-900 px-1 text-center whitespace-pre-wrap mr-2 ml-2">
{numeratorItem.name}
Expand Down
Loading

2 comments on commit a91d0f8

@vercel
Copy link

@vercel vercel bot commented on a91d0f8 Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on a91d0f8 Jan 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.