From f4f2450133bb4e38ab14f40a80fe8c087a27846d Mon Sep 17 00:00:00 2001 From: Misode Date: Tue, 19 Nov 2024 20:57:18 +0100 Subject: [PATCH] Refactor passing makeEdit around in ctx --- .../components/generator/McdocRenderer.tsx | 522 ++++++++++-------- src/app/components/generator/Tree.tsx | 7 +- 2 files changed, 305 insertions(+), 224 deletions(-) diff --git a/src/app/components/generator/McdocRenderer.tsx b/src/app/components/generator/McdocRenderer.tsx index 0ba6666e..0cefa8aa 100644 --- a/src/app/components/generator/McdocRenderer.tsx +++ b/src/app/components/generator/McdocRenderer.tsx @@ -1,5 +1,5 @@ import * as core from '@spyglassmc/core' -import type { JsonNode } from '@spyglassmc/json' +import type { JsonNode, JsonPairNode } from '@spyglassmc/json' import * as json from '@spyglassmc/json' import { JsonArrayNode, JsonBooleanNode, JsonNumberNode, JsonObjectNode, JsonStringNode } from '@spyglassmc/json' import { localeQuote } from '@spyglassmc/locales' @@ -18,7 +18,9 @@ import { ItemDisplay } from '../ItemDisplay.jsx' import { Octicon } from '../Octicon.jsx' import { formatIdentifier, getCategory, getChange, getDefault, getItemType, isDefaultCollapsedType, isFixedList, isInlineTuple, isListOrArray, isNumericType, isSelectRegistry, quickEqualTypes, simplifyType } from './McdocHelpers.js' -export interface McdocContext extends core.CheckerContext {} +export interface McdocContext extends core.CheckerContext { + makeEdit: MakeEdit +} type MakeEdit = (edit: (range: core.Range) => JsonNode | undefined) => void @@ -26,73 +28,72 @@ interface Props { type: Type optional?: boolean node: JsonNode | undefined - makeEdit: MakeEdit ctx: McdocContext } -export function McdocRoot({ type, node, makeEdit, ctx } : Props) { +export function McdocRoot({ type, node, ctx } : Props) { const { locale } = useLocale() if (type.kind === 'struct' && type.fields.length > 0 && JsonObjectNode.is(node)) { - return + return } return <>
- +
- + } -function Head({ type, optional, node, makeEdit, ctx }: Props) { +function Head({ type, optional, node, ctx }: Props) { if (type.kind === 'string') { - return + return } if (type.kind === 'enum') { - return + return } if (isNumericType(type)) { - return + return } if (type.kind === 'boolean') { - return + return } if (type.kind === 'union') { - return + return } if (type.kind === 'struct') { - return + return } if (isListOrArray(type)) { if (isFixedList(type)) { - return getItemType(type)), attributes: type.attributes }} optional={optional} node={node} makeEdit={makeEdit} ctx={ctx} /> + return getItemType(type)), attributes: type.attributes }} optional={optional} node={node} ctx={ctx} /> } - return + return } if (type.kind === 'tuple') { - return + return } if (type.kind === 'literal') { - return + return } if (type.kind === 'any' || type.kind === 'unsafe') { - return + return } return <> } -function Body({ type, optional, node, makeEdit, ctx }: Props) { +function Body({ type, optional, node, ctx }: Props) { if (type.kind === 'union') { - return + return } if (type.kind === 'struct') { if (!JsonObjectNode.is(node) || type.fields.length === 0) { return <> } return
- +
} if (isListOrArray(type)) { @@ -105,14 +106,14 @@ function Body({ type, optional, node, makeEdit, ctx }: Props } return
- +
} if (node.children?.length === 0) { return <> } return
- +
} if (type.kind === 'tuple') { @@ -120,18 +121,18 @@ function Body({ type, optional, node, makeEdit, ctx }: Props } return
- +
} if (type.kind === 'any' || type.kind === 'unsafe') { - return + return } return <> } const SPECIAL_UNSET = '__unset__' -function StringHead({ type, optional, node, makeEdit, ctx }: Props) { +function StringHead({ type, optional, node, ctx }: Props) { const { locale } = useLocale() const value = JsonStringNode.is(node) ? node.value : undefined @@ -151,7 +152,7 @@ function StringHead({ type, optional, node, makeEdit, ctx }: Props) if (value === newValue) { return } - makeEdit((range) => { + ctx.makeEdit((range) => { if ((newValue.length === 0 && optional) || (isSelect && newValue === SPECIAL_UNSET)) { return undefined } @@ -163,7 +164,7 @@ function StringHead({ type, optional, node, makeEdit, ctx }: Props) type: 'json:string', } }) - }, [optional, node, makeEdit, isSelect]) + }, [optional, node, ctx, isSelect]) const completions = useMemo(() => { return getValues(type, { ...ctx, offset: node?.range.start ?? 0 }) @@ -208,7 +209,7 @@ function StringHead({ type, optional, node, makeEdit, ctx }: Props) } -function EnumHead({ type, optional, node, makeEdit }: Props) { +function EnumHead({ type, optional, node, ctx }: Props) { const { locale } = useLocale() const value = JsonStringNode.is(node) ? node.value : (node && JsonNumberNode.is(node)) ? Number(node.value.value) : undefined @@ -217,7 +218,7 @@ function EnumHead({ type, optional, node, makeEdit }: Props) { if (value === newValue) { return } - makeEdit((range) => { + ctx.makeEdit((range) => { if (newValue === SPECIAL_UNSET) { return undefined } @@ -244,7 +245,7 @@ function EnumHead({ type, optional, node, makeEdit }: Props) { number.parent = result return result }) - }, [type.enumKind, value, makeEdit]) + }, [type.enumKind, value, ctx]) return } -function NumericHead({ type, node, makeEdit }: Props) { +function NumericHead({ type, node, ctx }: Props) { const { locale } = useLocale() const value = node && JsonNumberNode.is(node) ? Number(node.value.value) : undefined @@ -268,7 +269,7 @@ function NumericHead({ type, node, makeEdit }: Props) { if (number !== undefined && Number.isNaN(number)) { return } - makeEdit((range) => { + ctx.makeEdit((range) => { if (number === undefined) { return undefined } @@ -284,7 +285,7 @@ function NumericHead({ type, node, makeEdit }: Props) { newValue.parent = newNode return newNode }) - }, [isFloat, node, makeEdit]) + }, [isFloat, node, ctx]) const color = type.attributes?.find(a => a.name === 'color')?.value const colorKind = color?.kind === 'literal' && color.value.kind === 'string' ? color.value.value : undefined @@ -315,11 +316,11 @@ function NumericHead({ type, node, makeEdit }: Props) { } -function BooleanHead({ node, makeEdit }: Props) { +function BooleanHead({ node, ctx }: Props) { const value = node && JsonBooleanNode.is(node) ? node.value : undefined const onSelect = useCallback((newValue: boolean) => { - makeEdit((range) => { + ctx.makeEdit((range) => { if (value === newValue) { return undefined } @@ -329,7 +330,7 @@ function BooleanHead({ node, makeEdit }: Props) { value: newValue, } }) - }, [node, makeEdit, value]) + }, [node, ctx, value]) return <> @@ -337,7 +338,7 @@ function BooleanHead({ node, makeEdit }: Props) { } -function UnionHead({ type, optional, node, makeEdit, ctx }: Props>) { +function UnionHead({ type, optional, node, ctx }: Props>) { const { locale } = useLocale() if (type.members.length === 0) { @@ -347,7 +348,7 @@ function UnionHead({ type, optional, node, makeEdit, ctx }: Props { - makeEdit((range) => { + ctx.makeEdit((range) => { if (newValue === SPECIAL_UNSET) { return undefined } @@ -357,7 +358,7 @@ function UnionHead({ type, optional, node, makeEdit, ctx }: Props quickEqualTypes(m, selectedType)) : -1 @@ -368,7 +369,7 @@ function UnionHead({ type, optional, node, makeEdit, ctx }: Props{formatUnionMember(member, type.members.filter(m => m !== member))} )} - {selectedType && selectedType.kind !== 'literal' && } + {selectedType && selectedType.kind !== 'literal' && } } @@ -390,12 +391,12 @@ function formatUnionMember(type: SimplifiedMcdocTypeNoUnion, others: SimplifiedM return formatIdentifier(type.kind) } -function UnionBody({ type, optional, node, makeEdit, ctx }: Props>) { +function UnionBody({ type, optional, node, ctx }: Props>) { const selectedType = selectUnionMember(type, node) if (selectedType === undefined) { return <> } - return + return } function selectUnionMember(union: UnionType, node: JsonNode | undefined) { @@ -412,18 +413,18 @@ function selectUnionMember(union: UnionType, node: J return selectedType } -function StructHead({ type: outerType, optional, node, makeEdit, ctx }: Props) { +function StructHead({ type: outerType, optional, node, ctx }: Props) { const { locale } = useLocale() const type = node?.typeDef?.kind === 'struct' ? node.typeDef : outerType const onRemove = useCallback(() => { - makeEdit(() => { + ctx.makeEdit(() => { return undefined }) - }, [makeEdit]) + }, [ctx]) const onSetDefault = useCallback(() => { - makeEdit((range) => { + ctx.makeEdit((range) => { return getDefault(type, range, ctx) }) }, [type, ctx]) @@ -446,12 +447,11 @@ function StructHead({ type: outerType, optional, node, makeEdit, ctx }: Props } -function StructBody({ type: outerType, node, makeEdit, ctx }: Props) { +function StructBody({ type: outerType, node, ctx }: Props) { if (!JsonObjectNode.is(node)) { return <> } - const { locale } = useLocale() const { expand, collapse, isToggled } = useToggles() const type = node.typeDef?.kind === 'struct' ? node.typeDef : outerType @@ -479,61 +479,7 @@ function StructBody({ type: outerType, node, makeEdit, ctx }: Props { - if (pair) { - makeEdit(() => { - const newChild = edit(child?.range ?? core.Range.create(pair.range.end)) - if (newChild === undefined) { - node.children.splice(index, 1) - } else { - node.children[index] = { - type: 'pair', - range: pair.range, - key: pair.key, - value: newChild, - } - } - return node - }) - } else { - const newChild = edit(core.Range.create(node.range.end)) - if (newChild) { - makeEdit(() => { - node.children.push({ - type: 'pair', - range: newChild.range, - key: { - type: 'json:string', - range: newChild.range, - options: json.parser.JsonStringOptions, - value: key, - valueMap: [{ inner: core.Range.create(0), outer: newChild.range }], - }, - value: newChild, - }) - return node - }) - } - } - } - return
-
- {!field.optional && child === undefined && } - - - {canToggle && (isCollapsed - ? - : - )} - - {!isCollapsed && } -
- {!isCollapsed && } -
+ return })} {dynamicFields.map((field, index) => { if (field.key.kind === 'any' && field.type.kind === 'any') { @@ -544,7 +490,7 @@ function StructBody({ type: outerType, node, makeEdit, ctx }: Props
- +
})} @@ -556,10 +502,6 @@ function StructBody({ type: outerType, node, makeEdit, ctx }: Props } - const child = pair.value - const canToggle = JsonObjectNode.is(child) || JsonArrayNode.is(child) - const toggled = isToggled(key) - const isCollapsed = canToggle && (toggled === false || (toggled === undefined && (node.children.length - staticChilds.length) > 20)) // TODO: correctly determine which dynamic field this is a key for // Hack to support component negations, the only place with more than one dynamic field currently const field = dynamicFields[key.startsWith('!') ? 1 : 0] as SimplifiedStructTypePairField | undefined @@ -567,55 +509,94 @@ function StructBody({ type: outerType, node, makeEdit, ctx }: Props } - const childType = simplifyType(field.type, ctx, { key: pair.key, parent: node }) - const makeFieldEdit: MakeEdit = (edit) => { - makeEdit(() => { - const newChild = edit(child?.range ?? core.Range.create(pair.range.end)) - if (newChild === undefined) { - node.children.splice(index, 1) - } else { - node.children[index] = { - type: 'pair', - range: pair.range, - key: pair.key, - value: newChild, - } + return + })} + +} + +interface StaticFieldProps extends Props { + pair: JsonPairNode | undefined + index: number + field: SimplifiedStructTypePairField + fieldKey: string + isToggled: boolean | undefined + expand: (e: MouseEvent) => void + collapse: (e: MouseEvent) => void + node: JsonObjectNode +} +function StaticField({ pair, index, field, fieldKey, isToggled, expand, collapse, node, ctx }: StaticFieldProps) { + const { locale } = useLocale() + + const child = pair?.value + const childType = simplifyType(field.type, ctx, { key: pair?.key, parent: node }) + const canToggle = isDefaultCollapsedType(field.type) + const isCollapsed = canToggle && isToggled !== true + + const makeFieldEdit = useCallback((edit) => { + if (pair) { + ctx.makeEdit(() => { + const newChild = edit(child?.range ?? core.Range.create(pair.range.end)) + if (newChild === undefined) { + node.children.splice(index, 1) + } else { + node.children[index] = { + type: 'pair', + range: pair.range, + key: pair.key, + value: newChild, } + } + return node + }) + } else { + const newChild = edit(core.Range.create(node.range.end)) + if (newChild) { + ctx.makeEdit(() => { + node.children.push({ + type: 'pair', + range: newChild.range, + key: { + type: 'json:string', + range: newChild.range, + options: json.parser.JsonStringOptions, + value: fieldKey, + valueMap: [{ inner: core.Range.create(0), outer: newChild.range }], + }, + value: newChild, + }) return node }) } - const category = getCategory(field.type) - return
-
- - {canToggle && (isCollapsed - ? - : - )} - - - {!isCollapsed && } -
- {!isCollapsed && (childType.kind === 'struct' && category - ?
- -
- : )} -
- })} - + } + }, [pair, index, node, ctx]) + + const fieldCtx = useMemo(() => { + return { ...ctx, makeEdit: makeFieldEdit } + }, [ctx, makeFieldEdit]) + + return
+
+ {!field.optional && child === undefined && } + + + {canToggle && (isCollapsed + ? + : + )} + + {!isCollapsed && } +
+ {!isCollapsed && } +
} interface DynamicKeyProps { keyType: SimplifiedMcdocType valueType: McdocType parent: JsonObjectNode - makeEdit: MakeEdit ctx: McdocContext } -function DynamicKey({ keyType, valueType, parent, makeEdit, ctx }: DynamicKeyProps) { +function DynamicKey({ keyType, valueType, parent, ctx }: DynamicKeyProps) { const { locale } = useLocale() const [key, setKey] = useState('') @@ -632,9 +613,13 @@ function DynamicKey({ keyType, valueType, parent, makeEdit, ctx }: DynamicKeyPro } }, []) + const keyCtx = useMemo(() => { + return { ...ctx, makeEdit: makeKeyEdit } + }, [ctx, makeKeyEdit]) + const addKey = useCallback(() => { setKey('') - makeEdit((range) => { + ctx.makeEdit((range) => { const valueNode = getDefault(simplifyType(valueType, ctx, { key: keyNode, parent }), range, ctx) const newPair: core.PairNode = { type: 'pair', @@ -647,22 +632,83 @@ function DynamicKey({ keyType, valueType, parent, makeEdit, ctx }: DynamicKeyPro newPair.parent = parent return parent }) - }, [keyNode, makeEdit, ctx]) + }, [keyNode, ctx]) return <> - + } -function ListHead({ type, node, makeEdit, ctx }: Props) { +interface DynamicFieldProps extends Props { + pair: JsonPairNode + index: number + field: SimplifiedStructTypePairField + fieldKey: string + isToggled: boolean | undefined + expand: (e: MouseEvent) => void + collapse: (e: MouseEvent) => void + node: JsonObjectNode +} +function DynamicField({ pair, index, field, fieldKey, isToggled, expand, collapse, node, ctx }: DynamicFieldProps) { + const { locale } = useLocale() + + const child = pair.value + const canToggle = JsonObjectNode.is(child) || JsonArrayNode.is(child) + const isCollapsed = canToggle && (isToggled === false || (isToggled === undefined && (node.children.length - node.children.length) > 20)) + const childType = simplifyType(field.type, ctx, { key: pair.key, parent: node }) + const category = getCategory(field.type) + + const makeFieldEdit = useCallback((edit) => { + ctx.makeEdit(() => { + const newChild = edit(child?.range ?? core.Range.create(pair.range.end)) + if (newChild === undefined) { + node.children.splice(index, 1) + } else { + node.children[index] = { + type: 'pair', + range: pair.range, + key: pair.key, + value: newChild, + } + } + return node + }) + }, [pair, index, node, ctx]) + + const fieldCtx = useMemo(() => { + return { ...ctx, makeEdit: makeFieldEdit } + }, [ctx, makeFieldEdit]) + + return
+
+ + {canToggle && (isCollapsed + ? + : + )} + + + {!isCollapsed && } +
+ {!isCollapsed && (childType.kind === 'struct' && category + ?
+ +
+ : )} +
+} + +function ListHead({ type, node, ctx }: Props) { const { locale } = useLocale() const canAdd = (type.lengthRange?.max ?? Infinity) > (node?.children?.length ?? 0) const onAddTop = useCallback(() => { if (canAdd) { - makeEdit((range) => { + ctx.makeEdit((range) => { const itemType = simplifyType(getItemType(type), ctx) const newValue = getDefault(itemType, range, ctx) const newItem: core.ItemNode = { @@ -686,14 +732,14 @@ function ListHead({ type, node, makeEdit, ctx }: Props onAddTop()} disabled={!canAdd}> {Octicon.plus_circle} } -function ListBody({ type: outerType, node, makeEdit, ctx }: Props) { +function ListBody({ type: outerType, node, ctx }: Props) { if (!JsonArrayNode.is(node)) { return <> } @@ -710,7 +756,7 @@ function ListBody({ type: outerType, node, makeEdit, ctx }: Props { if (canAdd) { - makeEdit((range) => { + ctx.makeEdit((range) => { const itemType = simplifyType(getItemType(type), ctx) const newValue = getDefault(itemType, range, ctx) const newItem: core.ItemNode = { @@ -734,7 +780,7 @@ function ListBody({ type: outerType, node, makeEdit, ctx }: Props {node.children.map((item, index) => { @@ -749,7 +795,7 @@ function ListBody({ type: outerType, node, makeEdit, ctx }: Props } const key = index.toString() - return + return })} {node.children.length > 0 &&
} - {!isCollapsed && } + {!isCollapsed && } {!isCollapsed && (type.kind === 'struct' ?
- +
- : )} + : )} } -function TupleHead({ type, optional, node, makeEdit, ctx }: Props) { +function TupleHead({ type, optional, node, ctx }: Props) { const { locale } = useLocale() const isInline = isInlineTuple(type) const onRemove = useCallback(() => { - makeEdit(() => { + ctx.makeEdit(() => { return undefined }) - }, [makeEdit]) + }, [ctx]) const onSetDefault = useCallback(() => { - makeEdit((range) => { + ctx.makeEdit((range) => { return getDefault(type, range, ctx) }) }, [type, ctx]) @@ -902,26 +952,41 @@ function TupleHead({ type, optional, node, makeEdit, ctx }: Props) { const item = node?.children?.[index] const child = item?.value const childType = simplifyType(itemType, ctx) - const makeItemEdit: MakeEdit = (edit) => { - makeEdit((range) => { - const newChild = edit(child?.range ?? node?.range ?? range) - if (newChild === undefined) { - return node - } - node.children[index] = { - type: 'item', - range: newChild.range, - value: newChild, - } - return node - }) - } - return + return })} } -function TupleBody({ type, node, makeEdit, ctx }: Props) { +interface TupleHeadItemProps extends Props { + child: JsonNode | undefined + childType: SimplifiedMcdocType + index: number + node: JsonArrayNode +} +function TupleHeadItem({ child, childType, index, node, ctx }: TupleHeadItemProps) { + const makeItemEdit = useCallback((edit) => { + ctx.makeEdit((range) => { + const newChild = edit(child?.range ?? node?.range ?? range) + if (newChild === undefined) { + return node + } + node.children[index] = { + type: 'item', + range: newChild.range, + value: newChild, + } + return node + }) + }, [index, node, ctx]) + + const itemCtx = useMemo(() => { + return { ...ctx, makeEdit: makeItemEdit } + }, [ctx, makeItemEdit]) + + return +} + +function TupleBody({ type, node, ctx }: Props) { if (!JsonArrayNode.is(node)) { return <> } @@ -930,34 +995,49 @@ function TupleBody({ type, node, makeEdit, ctx }: Props) { const item = node?.children?.[index] const child = item?.value const childType = simplifyType(itemType, ctx) - const makeItemEdit: MakeEdit = (edit) => { - makeEdit(() => { - const newChild = edit(child?.range ?? node.range) - if (newChild === undefined) { - return node - } - node.children[index] = { - type: 'item', - range: newChild.range, - value: newChild, - } - return node - }) - } - return
-
- - - -
- -
+ return })} } -function LiteralHead({ type, optional, node, makeEdit, ctx }: Props) { - return +interface TupleBodyItemProps extends Props { + child: JsonNode | undefined + childType: SimplifiedMcdocType + index: number + node: JsonArrayNode +} +function TupleBodyItem({ child, childType, index, node, ctx }: TupleBodyItemProps) { + const makeItemEdit = useCallback((edit) => { + ctx.makeEdit(() => { + const newChild = edit(child?.range ?? node.range) + if (newChild === undefined) { + return node + } + node.children[index] = { + type: 'item', + range: newChild.range, + value: newChild, + } + return node + }) + }, [index, node, ctx]) + + const itemCtx = useMemo(() => { + return { ...ctx, makeEdit: makeItemEdit } + }, [ctx, makeItemEdit]) + + return
+
+ + + +
+ +
+} + +function LiteralHead({ type, optional, node, ctx }: Props) { + return } const ANY_TYPES: SimplifiedMcdocType[] = [ @@ -968,20 +1048,20 @@ const ANY_TYPES: SimplifiedMcdocType[] = [ { kind: 'struct', fields: [ { kind: 'pair', key: { kind: 'string' }, type: { kind: 'any' } }] }, ] -function AnyHead({ optional, node, makeEdit, ctx }: Props) { +function AnyHead({ optional, node, ctx }: Props) { const { locale } = useLocale() const selectedType = selectAnyType(node) const onSelect = useCallback((newValue: string) => { - makeEdit((range) => { + ctx.makeEdit((range) => { const newSelected = ANY_TYPES.find(t => t.kind === newValue) if (!newSelected) { return undefined } return getDefault(newSelected, range, ctx) }) - }, [makeEdit, ctx]) + }, [ctx]) return <> - {selectedType && } + {selectedType && } } -function AnyBody({ optional, node, makeEdit, ctx }: Props) { +function AnyBody({ optional, node, ctx }: Props) { const selectedType = selectAnyType(node) if (!selectedType) { return <> } - return + return } function selectAnyType(node: JsonNode | undefined) { diff --git a/src/app/components/generator/Tree.tsx b/src/app/components/generator/Tree.tsx index 417c7ae1..2717ac79 100644 --- a/src/app/components/generator/Tree.tsx +++ b/src/app/components/generator/Tree.tsx @@ -57,8 +57,9 @@ export function Tree({ docAndNode: original, onError }: TreePanelProps) { ...docAndNode.node.checkerErrors ?? [], ...docAndNode.node.linterErrors ?? [], ] - return service.getCheckerContext(docAndNode.doc, errors) - }, [docAndNode, service]) + const checkerCtx = service.getCheckerContext(docAndNode.doc, errors) + return { ...checkerCtx, makeEdit } + }, [docAndNode, service, makeEdit]) const resourceType = useMemo(() => { if (original.doc.uri.endsWith('/pack.mcmeta')) { @@ -81,7 +82,7 @@ export function Tree({ docAndNode: original, onError }: TreePanelProps) { }, [resourceType, ctx]) return
- {(ctx && mcdocType) && } + {(ctx && mcdocType) && }
}