Add grouping of module instances in Feature Tree (#6125)
* Rename operations to be more generic grouping * Add group enum * Add module instance groups * Change to export all operation ts-rs types to the same file * Fix Feature Tree display of modules to use name * Ignore clippy warning * Update output after operation changes * Change module instances in Feature Tree use to import icon * Fix error message when attempting to delete module instance
This commit is contained in:
@ -289,10 +289,7 @@ const OperationItem = (props: {
|
||||
send: Prop<Actor<typeof featureTreeMachine>, 'send'>
|
||||
}) => {
|
||||
const kclContext = useKclContext()
|
||||
const name =
|
||||
'name' in props.item && props.item.name !== null
|
||||
? getOperationLabel(props.item)
|
||||
: 'anonymous'
|
||||
const name = getOperationLabel(props.item)
|
||||
const errors = useMemo(() => {
|
||||
return kclContext.diagnostics.filter(
|
||||
(diag) =>
|
||||
@ -304,7 +301,7 @@ const OperationItem = (props: {
|
||||
}, [kclContext.diagnostics.length])
|
||||
|
||||
function selectOperation() {
|
||||
if (props.item.type === 'UserDefinedFunctionReturn') {
|
||||
if (props.item.type === 'GroupEnd') {
|
||||
return
|
||||
}
|
||||
props.send({
|
||||
@ -352,7 +349,7 @@ const OperationItem = (props: {
|
||||
function deleteOperation() {
|
||||
if (
|
||||
props.item.type === 'StdLibCall' ||
|
||||
props.item.type === 'UserDefinedFunctionCall' ||
|
||||
props.item.type === 'GroupBegin' ||
|
||||
props.item.type === 'KclStdLibCall'
|
||||
) {
|
||||
props.send({
|
||||
@ -368,7 +365,7 @@ const OperationItem = (props: {
|
||||
() => [
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
if (props.item.type === 'UserDefinedFunctionReturn') {
|
||||
if (props.item.type === 'GroupEnd') {
|
||||
return
|
||||
}
|
||||
props.send({
|
||||
@ -381,14 +378,19 @@ const OperationItem = (props: {
|
||||
>
|
||||
View KCL source code
|
||||
</ContextMenuItem>,
|
||||
...(props.item.type === 'UserDefinedFunctionCall'
|
||||
...(props.item.type === 'GroupBegin' &&
|
||||
props.item.group.type === 'FunctionCall'
|
||||
? [
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
if (props.item.type !== 'UserDefinedFunctionCall') {
|
||||
if (props.item.type !== 'GroupBegin') {
|
||||
return
|
||||
}
|
||||
const functionRange = props.item.functionSourceRange
|
||||
if (props.item.group.type !== 'FunctionCall') {
|
||||
// TODO: Add module instance support.
|
||||
return
|
||||
}
|
||||
const functionRange = props.item.group.functionSourceRange
|
||||
// For some reason, the cursor goes to the end of the source
|
||||
// range we select. So set the end equal to the beginning.
|
||||
functionRange[1] = functionRange[0]
|
||||
|
@ -1303,7 +1303,9 @@ export async function deleteFromSelection(
|
||||
if (!pathToNode) return new Error('Could not find extrude variable')
|
||||
} else {
|
||||
pathToNode = selection.codeRef.pathToNode
|
||||
if (varDec.node.type !== 'VariableDeclarator') {
|
||||
if (varDec.node.type === 'VariableDeclarator') {
|
||||
extrudeNameToDelete = varDec.node.id.name
|
||||
} else if (varDec.node.type === 'CallExpression') {
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
astClone,
|
||||
pathToNode,
|
||||
@ -1312,7 +1314,7 @@ export async function deleteFromSelection(
|
||||
if (err(callExp)) return callExp
|
||||
extrudeNameToDelete = callExp.node.callee.name.name
|
||||
} else {
|
||||
extrudeNameToDelete = varDec.node.id.name
|
||||
return new Error('Could not find extrude variable or call')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,17 +16,20 @@ function stdlib(name: string): Operation {
|
||||
|
||||
function userCall(name: string): Operation {
|
||||
return {
|
||||
type: 'UserDefinedFunctionCall',
|
||||
name,
|
||||
functionSourceRange: defaultSourceRange(),
|
||||
unlabeledArg: null,
|
||||
labeledArgs: {},
|
||||
type: 'GroupBegin',
|
||||
group: {
|
||||
type: 'FunctionCall',
|
||||
name,
|
||||
functionSourceRange: defaultSourceRange(),
|
||||
unlabeledArg: null,
|
||||
labeledArgs: {},
|
||||
},
|
||||
sourceRange: defaultSourceRange(),
|
||||
}
|
||||
}
|
||||
function userReturn(): Operation {
|
||||
return {
|
||||
type: 'UserDefinedFunctionReturn',
|
||||
type: 'GroupEnd',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import type { Operation } from '@rust/kcl-lib/bindings/Operation'
|
||||
import type { OpKclValue } from '@rust/kcl-lib/bindings/OpKclValue'
|
||||
import type { Operation, OpKclValue } from '@rust/kcl-lib/bindings/Operation'
|
||||
|
||||
import type { CustomIconName } from '@src/components/CustomIcon'
|
||||
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
||||
@ -1118,10 +1117,20 @@ export function getOperationLabel(op: Operation): string {
|
||||
return stdLibMap[op.name]?.label ?? op.name
|
||||
case 'KclStdLibCall':
|
||||
return stdLibMap[op.name]?.label ?? op.name
|
||||
case 'UserDefinedFunctionCall':
|
||||
return op.name ?? 'Anonymous custom function'
|
||||
case 'UserDefinedFunctionReturn':
|
||||
return 'User function return'
|
||||
case 'GroupBegin':
|
||||
if (op.group.type === 'FunctionCall') {
|
||||
return op.group.name ?? 'anonymous'
|
||||
} else if (op.group.type === 'ModuleInstance') {
|
||||
return op.group.name
|
||||
} else {
|
||||
const _exhaustiveCheck: never = op.group
|
||||
return '' // unreachable
|
||||
}
|
||||
case 'GroupEnd':
|
||||
return 'Group end'
|
||||
default:
|
||||
const _exhaustiveCheck: never = op
|
||||
return '' // unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@ -1134,8 +1143,16 @@ export function getOperationIcon(op: Operation): CustomIconName {
|
||||
return stdLibMap[op.name]?.icon ?? 'questionMark'
|
||||
case 'KclStdLibCall':
|
||||
return stdLibMap[op.name]?.icon ?? 'questionMark'
|
||||
default:
|
||||
case 'GroupBegin':
|
||||
if (op.group.type === 'ModuleInstance') {
|
||||
return 'import' // TODO: Use insert icon.
|
||||
}
|
||||
return 'make-variable'
|
||||
case 'GroupEnd':
|
||||
return 'questionMark'
|
||||
default:
|
||||
const _exhaustiveCheck: never = op
|
||||
return 'questionMark' // unreachable
|
||||
}
|
||||
}
|
||||
|
||||
@ -1151,31 +1168,31 @@ export function filterOperations(operations: Operation[]): Operation[] {
|
||||
* for use in the feature tree UI
|
||||
*/
|
||||
const operationFilters = [
|
||||
isNotUserFunctionWithNoOperations,
|
||||
isNotInsideUserFunction,
|
||||
isNotUserFunctionReturn,
|
||||
isNotGroupWithNoOperations,
|
||||
isNotInsideGroup,
|
||||
isNotGroupEnd,
|
||||
]
|
||||
|
||||
/**
|
||||
* A filter to exclude everything that occurs inside a UserDefinedFunctionCall
|
||||
* and its corresponding UserDefinedFunctionReturn from a list of operations.
|
||||
* This works even when there are nested function calls.
|
||||
* A filter to exclude everything that occurs inside a GroupBegin and its
|
||||
* corresponding GroupEnd from a list of operations. This works even when there
|
||||
* are nested function calls and module instances.
|
||||
*/
|
||||
function isNotInsideUserFunction(operations: Operation[]): Operation[] {
|
||||
function isNotInsideGroup(operations: Operation[]): Operation[] {
|
||||
const ops: Operation[] = []
|
||||
let depth = 0
|
||||
for (const op of operations) {
|
||||
if (depth === 0) {
|
||||
ops.push(op)
|
||||
}
|
||||
if (op.type === 'UserDefinedFunctionCall') {
|
||||
if (op.type === 'GroupBegin') {
|
||||
depth++
|
||||
}
|
||||
if (op.type === 'UserDefinedFunctionReturn') {
|
||||
if (op.type === 'GroupEnd') {
|
||||
depth--
|
||||
console.assert(
|
||||
depth >= 0,
|
||||
'Unbalanced UserDefinedFunctionCall and UserDefinedFunctionReturn; too many returns'
|
||||
'Unbalanced GroupBegin and GroupEnd; too many ends'
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1184,26 +1201,23 @@ function isNotInsideUserFunction(operations: Operation[]): Operation[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter to exclude UserDefinedFunctionCall operations and their
|
||||
* corresponding UserDefinedFunctionReturn that don't have any operations inside
|
||||
* them from a list of operations.
|
||||
* A filter to exclude GroupBegin operations and their corresponding GroupEnd
|
||||
* that don't have any operations inside them from a list of operations.
|
||||
*/
|
||||
function isNotUserFunctionWithNoOperations(
|
||||
operations: Operation[]
|
||||
): Operation[] {
|
||||
function isNotGroupWithNoOperations(operations: Operation[]): Operation[] {
|
||||
return operations.filter((op, index) => {
|
||||
if (
|
||||
op.type === 'UserDefinedFunctionCall' &&
|
||||
// If this is a call at the end of the array, it's preserved.
|
||||
op.type === 'GroupBegin' &&
|
||||
// If this is a begin at the end of the array, it's preserved.
|
||||
index < operations.length - 1 &&
|
||||
operations[index + 1].type === 'UserDefinedFunctionReturn'
|
||||
operations[index + 1].type === 'GroupEnd'
|
||||
)
|
||||
return false
|
||||
if (
|
||||
op.type === 'UserDefinedFunctionReturn' &&
|
||||
// If this return is at the beginning of the array, it's preserved.
|
||||
op.type === 'GroupEnd' &&
|
||||
// If this is an end at the beginning of the array, it's preserved.
|
||||
index > 0 &&
|
||||
operations[index - 1].type === 'UserDefinedFunctionCall'
|
||||
operations[index - 1].type === 'GroupBegin'
|
||||
)
|
||||
return false
|
||||
|
||||
@ -1212,11 +1226,10 @@ function isNotUserFunctionWithNoOperations(
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter to exclude UserDefinedFunctionReturn operations from a list of
|
||||
* operations.
|
||||
* A filter to exclude GroupEnd operations from a list of operations.
|
||||
*/
|
||||
function isNotUserFunctionReturn(ops: Operation[]): Operation[] {
|
||||
return ops.filter((op) => op.type !== 'UserDefinedFunctionReturn')
|
||||
function isNotGroupEnd(ops: Operation[]): Operation[] {
|
||||
return ops.filter((op) => op.type !== 'GroupEnd')
|
||||
}
|
||||
|
||||
export interface EnterEditFlowProps {
|
||||
@ -1230,7 +1243,7 @@ export async function enterEditFlow({
|
||||
}: EnterEditFlowProps): Promise<Error | CommandBarMachineEvent> {
|
||||
if (operation.type !== 'StdLibCall' && operation.type !== 'KclStdLibCall') {
|
||||
return new Error(
|
||||
'Feature tree editing not yet supported for user-defined functions. Please edit in the code editor.'
|
||||
'Feature tree editing not yet supported for user-defined functions or modules. Please edit in the code editor.'
|
||||
)
|
||||
}
|
||||
const stdLibInfo = stdLibMap[operation.name]
|
||||
@ -1269,7 +1282,7 @@ export async function enterAppearanceFlow({
|
||||
}: EnterEditFlowProps): Promise<Error | CommandBarMachineEvent> {
|
||||
if (operation.type !== 'StdLibCall' && operation.type !== 'KclStdLibCall') {
|
||||
return new Error(
|
||||
'Appearance setting not yet supported for user-defined functions. Please edit in the code editor.'
|
||||
'Appearance setting not yet supported for user-defined functions or modules. Please edit in the code editor.'
|
||||
)
|
||||
}
|
||||
const stdLibInfo = stdLibMap[operation.name]
|
||||
|
Reference in New Issue
Block a user