) {
: 'bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-chalkboard-20 dark:border-chalkboard-80'
}`}
>
+ {argName}
{argumentsToSubmit[argName] ? (
arg.inputType === 'selection' ? (
getSelectionTypeDisplayText(
argumentsToSubmit[argName] as Selections
)
+ ) : arg.inputType === 'kcl' ? (
+ roundOff(
+ Number(
+ (argumentsToSubmit[argName] as KclCommandValue)
+ .valueCalculated
+ ),
+ 4
+ )
) : typeof argumentsToSubmit[argName] === 'object' ? (
JSON.stringify(argumentsToSubmit[argName])
) : (
{argumentsToSubmit[argName] as ReactNode}
)
- ) : (
- {argName}
- )}
+ ) : null}
{showShortcuts && (
Hotkey:
{i + 1}
)}
+ {arg.inputType === 'kcl' &&
+ !!argumentsToSubmit[argName] &&
+ 'variableName' in
+ (argumentsToSubmit[argName] as KclCommandValue) && (
+ <>
+
+
+ New variable:{' '}
+ {
+ (
+ argumentsToSubmit[
+ argName
+ ] as KclExpressionWithVariable
+ ).variableName
+ }
+
+ >
+ )}
)
)}
diff --git a/src/components/CommandBar/CommandBarKclInput.module.css b/src/components/CommandBar/CommandBarKclInput.module.css
new file mode 100644
index 000000000..ff7902848
--- /dev/null
+++ b/src/components/CommandBar/CommandBarKclInput.module.css
@@ -0,0 +1,17 @@
+.editor {
+ @apply text-base flex-1;
+}
+
+.editor :global(.cm-editor) {
+ @apply bg-transparent;
+}
+
+.editor :global(.cm-line)::selection {
+ @apply px-1;
+ @apply text-chalkboard-100;
+ @apply bg-energy-10/50;
+}
+:global(.dark) .editor :global(.cm-line)::selection {
+ @apply text-energy-10;
+ @apply bg-energy-10/20;
+}
diff --git a/src/components/CommandBar/CommandBarKclInput.tsx b/src/components/CommandBar/CommandBarKclInput.tsx
new file mode 100644
index 000000000..067530a87
--- /dev/null
+++ b/src/components/CommandBar/CommandBarKclInput.tsx
@@ -0,0 +1,221 @@
+import { Completion } from '@codemirror/autocomplete'
+import { EditorState, EditorView, useCodeMirror } from '@uiw/react-codemirror'
+import { CustomIcon } from 'components/CustomIcon'
+import { useCommandsContext } from 'hooks/useCommandsContext'
+import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
+import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
+import { getSystemTheme } from 'lib/theme'
+import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
+import { roundOff } from 'lib/utils'
+import { varMentions } from 'lib/varCompletionExtension'
+import { useEffect, useRef, useState } from 'react'
+import { useHotkeys } from 'react-hotkeys-hook'
+import styles from './CommandBarKclInput.module.css'
+import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
+
+function CommandBarKclInput({
+ arg,
+ stepBack,
+ onSubmit,
+}: {
+ arg: CommandArgument & {
+ inputType: 'kcl'
+ name: string
+ }
+ stepBack: () => void
+ onSubmit: (event: unknown) => void
+}) {
+ const { commandBarSend, commandBarState } = useCommandsContext()
+ const previouslySetValue = commandBarState.context.argumentsToSubmit[
+ arg.name
+ ] as KclCommandValue | undefined
+ const { settings } = useGlobalStateContext()
+ const defaultValue = (arg.defaultValue as string) || ''
+ const [value, setValue] = useState(
+ previouslySetValue?.valueText || defaultValue || ''
+ )
+ const [createNewVariable, setCreateNewVariable] = useState(
+ previouslySetValue && 'variableName' in previouslySetValue
+ )
+ const [canSubmit, setCanSubmit] = useState(true)
+ useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
+ const editorRef = useRef(null)
+
+ const {
+ prevVariables,
+ calcResult,
+ newVariableInsertIndex,
+ valueNode,
+ newVariableName,
+ setNewVariableName,
+ isNewVariableNameUnique,
+ } = useCalculateKclExpression({
+ value,
+ initialVariableName:
+ previouslySetValue && 'variableName' in previouslySetValue
+ ? previouslySetValue.variableName
+ : arg.name,
+ })
+ const varMentionData: Completion[] = prevVariables.map((v) => ({
+ label: v.key,
+ detail: String(roundOff(v.value as number)),
+ }))
+
+ const { setContainer } = useCodeMirror({
+ container: editorRef.current,
+ value,
+ indentWithTab: false,
+ basicSetup: false,
+ autoFocus: true,
+ selection: {
+ anchor: 0,
+ head:
+ previouslySetValue && 'valueText' in previouslySetValue
+ ? previouslySetValue.valueText.length
+ : defaultValue.length,
+ },
+ accessKey: 'command-bar',
+ theme:
+ settings.context.theme === 'system'
+ ? getSystemTheme()
+ : settings.context.theme,
+ extensions: [
+ EditorView.domEventHandlers({
+ keydown: (event) => {
+ if (event.key === 'Backspace' && value === '') {
+ event.preventDefault()
+ stepBack()
+ }
+ },
+ }),
+ varMentions(varMentionData),
+ EditorState.transactionFilter.of((tr) => {
+ if (tr.newDoc.lines > 1) {
+ handleSubmit()
+ return []
+ }
+ return tr
+ }),
+ ],
+ onChange: (newValue) => setValue(newValue),
+ })
+
+ useEffect(() => {
+ if (editorRef.current) {
+ setContainer(editorRef.current)
+ }
+ }, [arg, editorRef])
+
+ useEffect(() => {
+ setCanSubmit(
+ calcResult !== 'NAN' && (!createNewVariable || isNewVariableNameUnique)
+ )
+ }, [calcResult, createNewVariable, isNewVariableNameUnique])
+
+ function handleSubmit(e?: React.FormEvent) {
+ e?.preventDefault()
+ if (!canSubmit || valueNode === null) return
+
+ onSubmit(
+ createNewVariable
+ ? ({
+ valueAst: valueNode,
+ valueText: value,
+ valueCalculated: calcResult,
+ variableName: newVariableName,
+ insertIndex: newVariableInsertIndex,
+ variableIdentifierAst: createIdentifier(newVariableName),
+ variableDeclarationAst: createVariableDeclaration(
+ newVariableName,
+ valueNode
+ ),
+ } satisfies KclCommandValue)
+ : ({
+ valueAst: valueNode,
+ valueText: value,
+ valueCalculated: calcResult,
+ } satisfies KclCommandValue)
+ )
+ }
+
+ return (
+
+ )
+}
+
+export default CommandBarKclInput
diff --git a/src/components/CommandBar/CommandBarReview.tsx b/src/components/CommandBar/CommandBarReview.tsx
index 4764955f6..8c047b2c0 100644
--- a/src/components/CommandBar/CommandBarReview.tsx
+++ b/src/components/CommandBar/CommandBarReview.tsx
@@ -14,7 +14,18 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
})
useHotkeys(
- '1, 2, 3, 4, 5, 6, 7, 8, 9, 0',
+ [
+ 'alt+1',
+ 'alt+2',
+ 'alt+3',
+ 'alt+4',
+ 'alt+5',
+ 'alt+6',
+ 'alt+7',
+ 'alt+8',
+ 'alt+9',
+ 'alt+0',
+ ],
(_, b) => {
if (b.keys && !Number.isNaN(parseInt(b.keys[0], 10))) {
if (!selectedCommand?.args) return
diff --git a/src/components/CustomIcon.tsx b/src/components/CustomIcon.tsx
index 61772843c..e59241584 100644
--- a/src/components/CustomIcon.tsx
+++ b/src/components/CustomIcon.tsx
@@ -18,10 +18,12 @@ export type CustomIconName =
| 'horizontal'
| 'horizontalDash'
| 'line'
+ | 'make-variable'
| 'move'
| 'network'
| 'networkCrossedOut'
| 'parallel'
+ | 'plus'
| 'search'
| 'settings'
| 'sketch'
@@ -336,6 +338,22 @@ export const CustomIcon = ({
/>
)
+ case 'make-variable':
+ return (
+
+ )
case 'move':
return (
)
+ case 'plus':
+ return (
+
+ )
case 'search':
return (