Add edit flow for named constants / parameters (#5911)

* Add support for forcing kcl input create variable

* Command palette padding tweak

* Make traverse function work for ExpressionStatements

* Add utilities for getting earliest safe index in AST

* Fix the insertIndex logic to not be based on the selection anymore

* Add workflow to create a named constant

* Fix bug with nameEndInDigits matcher

* Tweak command config

* Add a three-dot menu to feature tree pane to create parameters

* Add E2E test for create parameter flow

* Remove edit flow oops

* Fix tsc error

* Fix E2E test

* Update named constant position in edit flow test

* Add tags into consideration for safe insert index

Per @Irev-dev's helpful feedback, with unit tests!

* Fix tsc by removing a generic type

* Remove unused imports

* Fix lints

* A snapshot a day keeps the bugs away! 📷🐛

* Add utilities for working with variable declarations

* Add "edit parameter" user flow

* Add edit flow config

* WIP working on de-bloating useCalculateKclExpreesion

* Add the ability to specify a `displayName` for an arg

* Add utility to type check on SourceRanges

* Review step design tweak fixes

* Refactor useCalculateKclExpression to take a sourceRange

* Make option arg validation work for objects and arrays

Using an admittedly dumb stringification approach

* Make edit flow never move the constant to be edited

* Add E2E test section

* Fix lints

* Remove lying comment, tiny CSS tweak

* A snapshot a day keeps the bugs away! 📷🐛

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Frank Noirot
2025-03-20 16:41:09 -04:00
committed by GitHub
parent 2c6404f671
commit 9da8574103
18 changed files with 301 additions and 29 deletions

View File

@ -54,7 +54,7 @@ function ArgumentInput({
return (
<CommandArgOptionInput
arg={arg}
argName={arg.name}
argName={arg.displayName || arg.name}
stepBack={stepBack}
onSubmit={onSubmit}
placeholder="Select an option"
@ -71,7 +71,7 @@ function ArgumentInput({
{ name: 'Off', value: false },
],
}}
argName={arg.name}
argName={arg.displayName || arg.name}
stepBack={stepBack}
onSubmit={onSubmit}
placeholder="Select an option"

View File

@ -38,7 +38,7 @@ function CommandBarBasicInput({
className="flex items-center mx-4 my-4"
>
<span className="capitalize px-2 py-1 rounded-l bg-chalkboard-100 dark:bg-chalkboard-80 text-chalkboard-10 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80">
{arg.name}
{arg.displayName || arg.name}
</span>
<input
data-testid="cmd-bar-arg-value"

View File

@ -130,7 +130,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
data-test-name="arg-name"
className="capitalize"
>
{argName}
{arg.displayName || argName}
</span>
<span className="sr-only">:&nbsp;</span>
<span data-testid="header-arg-value">

View File

@ -21,10 +21,16 @@ import { useSelector } from '@xstate/react'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useSettings } from 'machines/appMachine'
import toast from 'react-hot-toast'
import { AnyStateMachine, SnapshotFrom } from 'xstate'
import { kclManager } from 'lib/singletons'
import { getNodeFromPath } from 'lang/queryAst'
import { isPathToNode, SourceRange, VariableDeclarator } from 'lang/wasm'
import { Node } from '@rust/kcl-lib/bindings/Node'
import { err } from 'lib/trap'
const machineContextSelector = (snapshot?: {
context: Record<string, unknown>
}) => snapshot?.context
// TODO: remove the need for this selector once we decouple all actors from React
const machineContextSelector = (snapshot?: SnapshotFrom<AnyStateMachine>) =>
snapshot?.context
function CommandBarKclInput({
arg,
@ -47,6 +53,16 @@ function CommandBarKclInput({
arg.machineActor,
machineContextSelector
)
const sourceRangeForPrevVariables = useMemo<SourceRange | undefined>(() => {
const nodeToEdit = commandBarState.context.argumentsToSubmit.nodeToEdit
const pathToNode = isPathToNode(nodeToEdit) ? nodeToEdit : undefined
const node = pathToNode
? getNodeFromPath<Node<VariableDeclarator>>(kclManager.ast, pathToNode)
: undefined
return !err(node) && node && node.node.type === 'VariableDeclarator'
? [node.node.start, node.node.end, node.node.moduleId]
: undefined
}, [kclManager.ast, commandBarState.context.argumentsToSubmit.nodeToEdit])
const defaultValue = useMemo(
() =>
arg.defaultValue
@ -90,21 +106,22 @@ function CommandBarKclInput({
const editorRef = useRef<HTMLDivElement>(null)
const {
prevVariables,
calcResult,
newVariableInsertIndex,
valueNode,
newVariableName,
setNewVariableName,
isNewVariableNameUnique,
prevVariables,
} = useCalculateKclExpression({
value,
initialVariableName,
sourceRange: sourceRangeForPrevVariables,
})
const varMentionData: Completion[] = prevVariables.map((v) => ({
label: v.key,
detail: String(roundOff(v.value as number)),
detail: String(roundOff(Number(v.value))),
}))
const varMentionsExtension = varMentions(varMentionData)
@ -219,13 +236,18 @@ function CommandBarKclInput({
}
return (
<form id="arg-form" onSubmit={handleSubmit} data-can-submit={canSubmit}>
<form
id="arg-form"
className="mb-2"
onSubmit={handleSubmit}
data-can-submit={canSubmit}
>
<label className="flex gap-4 items-center mx-4 my-4 border-solid border-b border-chalkboard-50">
<span
data-testid="cmd-bar-arg-name"
className="capitalize text-chalkboard-80 dark:text-chalkboard-20"
>
{arg.name}
{arg.displayName || arg.name}
</span>
<div
data-testid="cmd-bar-arg-value"
@ -249,7 +271,7 @@ function CommandBarKclInput({
</span>
</label>
{createNewVariable ? (
<div className="flex mb-2 items-baseline gap-4 mx-4 border-solid border-0 border-b border-chalkboard-50">
<div className="flex items-baseline gap-4 mx-4 border-solid border-0 border-b border-chalkboard-50">
<label
htmlFor="variable-name"
className="text-base text-chalkboard-80 dark:text-chalkboard-20"

View File

@ -58,7 +58,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
return (
<CommandBarHeader>
<p className="px-4">
<p className="px-4 pb-2">
{selectedCommand?.reviewMessage ? (
selectedCommand.reviewMessage instanceof Function ? (
selectedCommand.reviewMessage(commandBarState.context)
@ -66,7 +66,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
selectedCommand.reviewMessage
)
) : (
<>Confirm {selectedCommand?.name}</>
<>Confirm {selectedCommand?.displayName || selectedCommand?.name}</>
)}
</p>
<form

View File

@ -40,7 +40,7 @@ function CommandBarTextareaInput({
data-testid="cmd-bar-arg-name"
className="capitalize px-2 py-1 rounded-br bg-chalkboard-100 dark:bg-chalkboard-80 text-chalkboard-10 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80"
>
{arg.name}
{arg.displayName || arg.name}
</span>
<textarea
data-testid="cmd-bar-arg-value"