Skip to content

Commit

Permalink
Merge pull request #2776 from quantified-uncertainty/improved-tooltips
Browse files Browse the repository at this point in the history
Tooltips for top-level variables
  • Loading branch information
berekuk authored Jan 19, 2024
2 parents 0baa7f7 + 994ecad commit 844a9bf
Show file tree
Hide file tree
Showing 24 changed files with 374 additions and 223 deletions.
2 changes: 1 addition & 1 deletion packages/components/src/components/CodeEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type CodeEditorProps = {
showGutter?: boolean;
lineWrapping?: boolean;
errors?: SqError[];
sourceId?: string;
sourceId: string;
fontSize?: number;
project: SqProject;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,53 @@ import { SqProject } from "@quri/squiggle-lang";

import { FnDocumentationFromName } from "../../ui/FnDocumentation.js";

export function getNameNodes(tree: Tree, from: number) {
type NameNode = {
node: SyntaxNode;
type: "function" | "variable";
};

export function getNameNodes(tree: Tree, from: number): NameNode[] {
const cursor = tree.cursorAt(from, -1);
const nameNodes: SyntaxNode[] = [];
const nameNodes: NameNode[] = [];

// We walk up and backwards through the tree, looking for nodes that have names.

let direction: "start" | "sibling" | "parent" | undefined = "start";
while (1) {
// Only for sibling nodes; `foo = { <cursor> }` shouldn't autocomplete `foo`.
if (cursor.type.is("Binding") && direction === "sibling") {
const nameNode = cursor.node.getChild("VariableName");
if (nameNode) {
nameNodes.push(nameNode);
if (cursor.type.is("Statement") && direction === "sibling") {
// Only for sibling nodes; `foo = { <cursor> }` shouldn't autocomplete `foo`.

// Unwrap decorated statements.
let node: SyntaxNode | null = cursor.node;
while (node && node.type.is("DecoratedStatement")) {
node = node.getChild("Statement");
}
// Only for sibling nodes; Squiggle doesn't support recursive calls.
} else if (cursor.type.is("FunDeclaration") && direction === "sibling") {
const nameNode = cursor.node.getChild("FunctionName");
if (nameNode) {
nameNodes.push(nameNode);

const nameNode = node?.getChild("VariableName");
if (node && nameNode) {
nameNodes.push({
node: nameNode,
type: node?.type.is("DefunStatement") ? "function" : "variable",
});
}
} else if (cursor.type.is("FunDeclaration") && direction !== "sibling") {
} else if (cursor.type.is("DefunStatement") && direction !== "sibling") {
// Function declaration that's a parent, let's autocomplete its parameter names.
// Note that we also allow `direction === "start"`, to handle `f(foo) = foo` correctly.
const parameterNodes =
cursor.node.getChild("LambdaArgs")?.getChildren("LambdaParameter") ??
[];

for (const parameter of parameterNodes) {
const nameNode = parameter.getChild("LambdaParameterName");
if (nameNode) {
nameNodes.push(nameNode);
nameNodes.push({
node: nameNode,
// Is there a more specific type? There's no "parameter" type in CodeMirror.
// https://codemirror.net/docs/ref/#autocomplete.Completion.type
type: "variable",
});
}
}
} else if (cursor.type.is("Decorator") && direction !== "sibling") {
// TODO
}

// Move to the next node and store the direction that we used.
Expand Down Expand Up @@ -106,24 +120,26 @@ export function makeCompletionSource(project: SqProject) {
snippetCompletion("|${args}| ${body}", {
label: "|",
detail: "lambda function",
type: "syntax",
type: "text",
}),
],
};
}
}

{
const identifier = cmpl.tokenBefore(["AccessExpr", "IdentifierExpr"]);
const identifier = cmpl.tokenBefore(["AccessExpr", "Identifier"]);
if (identifier) {
const { from } = identifier;
const nameNodes = getNameNodes(tree, from);
const localCompletions = nameNodes.map((node): Completion => {
const name = cmpl.state.doc.sliceString(node.from, node.to);
const type = node.type.is("FunctionName") ? "function" : "variable";
const localCompletions = nameNodes.map((nameNode): Completion => {
const name = cmpl.state.doc.sliceString(
nameNode.node.from,
nameNode.node.to
);
return {
label: name,
type,
type: nameNode.type,
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ export const lightThemeHighlightingStyle = HighlightStyle.define([
],
color: numbers,
},
{
tag: [tags.escape],
color: escapes,
},
{ tag: tags.escape, color: escapes },
{
tag: [
tags.operator,
Expand All @@ -67,7 +64,7 @@ export const lightThemeHighlightingStyle = HighlightStyle.define([
fontWeight: "bold",
color: operators,
},
{ tag: [tags.meta, tags.comment], color: comments },
{ tag: tags.comment, color: comments },
{ tag: tags.strong, fontWeight: "bold" },
{ tag: tags.emphasis, fontStyle: "italic" },
{ tag: tags.strikethrough, textDecoration: "line-through" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ commaSep<content> {
// when trailing comma is allowed
commaSep1<content> { "" | content ("," content?)* }

Binding { export? VariableName { identifier } "=" expression }
LetStatement { export? VariableName { identifier } "=" expression }

LambdaParameter {
LambdaParameterName { identifier } (":" expression)?
Expand All @@ -50,7 +50,7 @@ LambdaArgs {
() | LambdaParameter ("," LambdaParameter)*
}

FunDeclaration { export? FunctionName { identifier } ~callOrDeclaration "(" LambdaArgs ")" "=" expression }
DefunStatement { export? VariableName { identifier } ~callOrDeclaration "(" LambdaArgs ")" "=" expression }

Decorator {
"@" DecoratorName { identifier }
Expand All @@ -60,64 +60,62 @@ Decorator {
)
}

statement {
Decorator*
(
Binding
| FunDeclaration
)
statement[@isGroup="Statement"] {
LetStatement
| DefunStatement
| DecoratedStatement { Decorator statement }
}

expression {
expressionWithoutParens
| ( "(" expression ")" )
}

expression[@isGroup="Expression"] {
expressionWithoutParens[@isGroup="Expression"] {
String
| Boolean
| Boolean { @specialize[@name="Boolean"]<identifier, "true" | "false"> }
| Number
| BlockExpr { "{" blockContent "}" }
| DictExpr {
| Block { "{" blockContent "}" }
| Dict {
"{"
commaSep1<
Entry { Field[@dynamicPrecedence=1] { expression } ~inheritAmbig ":" expression }
KeyValue { Field[@dynamicPrecedence=1] { expression } ~inheritAmbig ":" expression }
| InheritEntry { Field[@dynamicPrecedence=0] { identifier } ~inheritAmbig }
>
"}"
}
| LambdaExpr { "{" ArgsOpen { "|" } LambdaArgs "|" blockContent "}" }
| IfExpr { if expression then expression !else else expression }
| ParenExpr { "(" expression ")" }
| IdentifierExpr { identifier }
| Lambda { "{" ArgsOpen { "|" } LambdaArgs "|" blockContent "}" }
| Identifier { identifier }
| AccessExpr { expression !deref "." Field { identifier } }
| CallExpr { expression ~callOrDeclaration !call "(" commaSep<Argument { expression }> ")" }
| TernaryExpr { expression !logop LogicOp<"?"> expression LogicOp<":"> expression }
| KVAccessExpr { expression !call ("[" Key { expression } "]") }
| ArrayExpr { "[" commaSep1<expression> "]" }
| UnaryExpr { !unary (ArithOp<"-"> | ArithOp<"!"> | DotArithOp<".-">) expression }
| LogicExpr {
| Call { expression ~callOrDeclaration !call "(" commaSep<Argument { expression }> ")" }
| TernaryC { expression !logop LogicOp<"?"> expression LogicOp<":"> expression }
| TernaryIfThenElse { if expression then expression !else else expression }
| BracketLookup { expression !call ("[" Key { expression } "]") }
| Array { "[" commaSep1<expression> "]" }
| UnaryCall { !unary (ArithOp<"-"> | ArithOp<"!"> | DotArithOp<".-">) expression }
| InfixCall {
expression !or LogicOp<"||"> expression
| expression !and LogicOp<"&&"> expression
| expression !rel LogicOp<">"> expression
| expression !rel LogicOp<"<"> expression
| expression !rel LogicOp<"<="> expression
| expression !rel LogicOp<">="> expression
| expression !rel LogicOp<"=="> expression
}
| ControlExpr {
expression !control ControlOp<"->"> expression
}
| ArithExpr {
expression !times ( ArithOp<"*"> | DotArithOp<".*"> ) expression
| expression !times ( ArithOp<"*"> | DotArithOp<".*"> ) expression
| expression !times ( ArithOp<"/"> | DotArithOp<"./"> ) expression
| expression !exp ( ArithOp<"^"> | DotArithOp<".^"> ) expression
| expression !plus ( ArithOp<"+"> | DotArithOp<".+"> ) expression
| expression !plus ( ArithOp<"-"> | DotArithOp<".-"> ) expression
| expression !plus @extend[@name="ArithOp"]<identifier, "to"> expression
}
| Pipe {
expression !control ControlOp<"->"> expression
}
}

// use `@extend` instead of `@specialize`, because keywords are valid variable names in Squiggle, for now.
kw<term> { @extend[@name={term}]<identifier, term> }


Boolean { @specialize[@name="Boolean"]<identifier, "true" | "false"> }

kw<term> { @specialize[@name={term}]<identifier, term> }
if { kw<"if"> }
then { kw<"then"> }
else { kw<"else"> }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,9 @@ import { parser } from "./generated/squiggle.js";
const parserWithMetadata = parser.configure({
props: [
styleTags({
if: t.keyword,
then: t.keyword,
else: t.keyword,
import: t.keyword,
export: t.keyword,
as: t.keyword,
"if then else import export as": t.keyword,

Equals: t.definitionOperator,

ArithOp: t.arithmeticOperator,
LogicOp: t.logicOperator,
ControlOp: t.controlOperator,
Expand All @@ -38,40 +32,36 @@ const parserWithMetadata = parser.configure({
Boolean: t.bool,
Number: t.integer,
String: t.string,
Comment: t.comment,
Void: t.escape,
LineComment: t.lineComment,
BlockComment: t.blockComment,
Escape: t.escape,

FunctionName: t.function(t.variableName),

DecoratorName: t.variableName,
"Decorator/*/String": t.comment,
At: t.keyword,

LambdaSyntax: t.blockComment,

VariableName: t.constant(t.variableName),
IdentifierExpr: t.variableName,
Identifier: t.variableName,
Field: t.variableName,
LambdaParameterName: t.variableName,
}),
foldNodeProp.add({
LambdaExpr: (context) => ({
Lambda: (context) => ({
from: context.getChild("NonEmptyProgram")?.from || 0,
to: context.getChild("NonEmptyProgram")?.to || 0,
}),
BlockExpr: foldInside,
DictExpr: foldInside,
ArrayExpr: foldInside,
Block: foldInside,
Dict: foldInside,
Array: foldInside,
}),
indentNodeProp.add({
DictExpr: (context) =>
Dict: (context) =>
context.baseIndent + (context.textAfter === "}" ? 0 : context.unit),
BlockExpr: (context) =>
Block: (context) =>
context.baseIndent + (context.textAfter === "}" ? 0 : context.unit),
LambdaExpr: (context) =>
Lambda: (context) =>
context.baseIndent + (context.textAfter === "}" ? 0 : context.unit),
ArrayExpr: (context) =>
Array: (context) =>
context.baseIndent + (context.textAfter === "]" ? 0 : context.unit),
}),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ export function useSquiggleEditorExtensions(
const formatExtension = useFormatSquiggleExtension();
const errorsExtension = useErrorsExtension(view, params.errors);

const tooltipsExtension = useTooltipsExtension();
const tooltipsExtension = useTooltipsExtension(view, {
project: params.project,
sourceId: params.sourceId,
});

const highPrioritySquiggleExtensions = [
submitExtension, // works only if listed before `builtinExtensions`
Expand Down
Loading

2 comments on commit 844a9bf

@vercel
Copy link

@vercel vercel bot commented on 844a9bf Jan 19, 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 844a9bf Jan 19, 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.