= {
type: 'item',
range,
children: [newValue],
value: newValue,
}
newValue.parent = newItem
if (JsonArrayNode.is(node)) {
node.children.unshift(newItem)
newItem.parent = node
return node
}
const newArray: JsonArrayNode = {
type: 'json:array',
range,
children: [newItem],
}
newItem.parent = newArray
return newArray
})
}
}, [type, node, makeEdit, ctx, canAdd])
return
}
function ListBody({ type: outerType, node, makeEdit, ctx }: Props) {
if (!JsonArrayNode.is(node)) {
return <>>
}
const { locale } = useLocale()
const { expand, collapse, isToggled } = useToggles()
const type = node.typeDef && isListOrArray(node.typeDef) ? node.typeDef : outerType
const canAdd = (type.lengthRange?.max ?? Infinity) > (node?.children?.length ?? 0)
const onRemoveItem = useCallback((index: number) => {
makeEdit(() => {
node.children.splice(index, 1)
return node
})
}, [makeEdit, node])
const onMoveUp = useCallback((index: number) => {
if (node.children.length <= 1 || index <= 0) {
return
}
makeEdit(() => {
const moved = node.children.splice(index, 1)
node.children.splice(index - 1, 0, ...moved)
return node
})
}, [makeEdit])
const onMoveDown = useCallback((index: number) => {
if (node.children.length <= 1 || index >= node.children.length - 1) {
return
}
makeEdit(() => {
const moved = node.children.splice(index, 1)
node.children.splice(index + 1, 0, ...moved)
return node
})
}, [makeEdit])
const onAddBottom = useCallback(() => {
if (canAdd) {
makeEdit((range) => {
const itemType = simplifyType(getItemType(type), ctx)
const newValue = getDefault(itemType, range, ctx)
const newItem: core.ItemNode = {
type: 'item',
range,
children: [newValue],
value: newValue,
}
newValue.parent = newItem
if (JsonArrayNode.is(node)) {
node.children.push(newItem)
newItem.parent = node
return node
}
const newArray: JsonArrayNode = {
type: 'json:array',
range,
children: [newItem],
}
newItem.parent = newArray
return newArray
})
}
}, [type, node, makeEdit, ctx, canAdd])
return <>
{node.children.map((item, index) => {
const child = item.value
const itemType = getItemType(type)
const childType = simplifyType(itemType, ctx)
const canToggle = JsonObjectNode.is(child)
const toggled = isToggled(index.toString())
const isCollapsed = canToggle && (toggled === false || (toggled === undefined && node.children.length > 20))
const makeItemEdit: MakeEdit = (edit) => {
makeEdit(() => {
const newChild = edit(child?.range ?? item.range)
node.children[index] = {
type: 'item',
range: item.range,
value: newChild,
}
return node
})
}
const canMoveUp = node.children.length > 1 && index > 0
const canMoveDown = node.children.length > 1 && index < (node.children.length - 1)
return
}
{!isCollapsed && (childType.kind === 'struct'
?
: )}
})}
{node.children.length > 0 && }
>
}
function TupleHead({ type, optional, node, makeEdit, ctx }: Props) {
const { locale } = useLocale()
const isInline = isInlineTuple(type)
const onRemove = useCallback(() => {
makeEdit(() => {
return undefined
})
}, [makeEdit])
const onSetDefault = useCallback(() => {
makeEdit((range) => {
return getDefault(type, range, ctx)
})
}, [type, ctx])
return <>
{optional
? (JsonArrayNode.is(node)
?
: )
: (!JsonArrayNode.is(node)
?
: <>>
)}
{isInline && JsonArrayNode.is(node) && type.items.map((itemType, index) => {
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
})}
>
}
function TupleBody({ type, node, makeEdit, ctx }: Props) {
if (!JsonArrayNode.is(node)) {
return <>>
}
return <>
{type.items.map((itemType, index) => {
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
})}
>
}
function LiteralHead({ type, optional, node, makeEdit, ctx }: Props) {
return
}
const ANY_TYPES: SimplifiedMcdocType[] = [
{ kind: 'boolean' },
{ kind: 'double' },
{ kind: 'string' },
{ kind: 'list', item: { kind: 'any' } },
{ kind: 'struct', fields: [ { kind: 'pair', key: { kind: 'string' }, type: { kind: 'any' } }] },
]
function AnyHead({ optional, node, makeEdit, ctx }: Props) {
const { locale } = useLocale()
const selectedType = selectAnyType(node)
const onSelect = useCallback((newValue: string) => {
makeEdit((range) => {
const newSelected = ANY_TYPES.find(t => t.kind === newValue)
if (!newSelected) {
return undefined
}
return getDefault(newSelected, range, ctx)
})
}, [makeEdit, ctx])
return <>
{selectedType && }
>
}
function AnyBody({ optional, node, makeEdit, ctx }: Props) {
const selectedType = selectAnyType(node)
if (!selectedType) {
return <>>
}
return
}
function selectAnyType(node: JsonNode | undefined) {
switch (node?.type) {
case 'json:boolean': return ANY_TYPES[0]
case 'json:number': return ANY_TYPES[1]
case 'json:string': return ANY_TYPES[2]
case 'json:array': return ANY_TYPES[3]
case 'json:object': return ANY_TYPES[4]
default: return undefined
}
}
interface KeyProps {
label: string | number | boolean
raw?: boolean
}
function Key({ label, raw }: KeyProps) {
return
}
interface ErrorsProps {
type: SimplifiedMcdocType
node: JsonNode | undefined
ctx: McdocContext
}
function Errors({ type, node, ctx }: ErrorsProps) {
const errors = useMemo(() => {
if (node === undefined) {
return []
}
const errors = ctx.err.errors
// Get all errors inside the current node
.filter(e => core.Range.containsRange(node.range, e.range, true))
// Unless they are inside a child node
.filter(e => !node.children?.some(c => (c.type === 'item' || c.type === 'pair') && core.Range.containsRange(c.range, e.range, true)))
// Filter out "Missing key" errors
.filter(e => !(core.Range.length(e.range) === 1 && (type.kind === 'struct' || (type.kind === 'union' && (selectUnionMember(type, node) ?? type.members[0]).kind === 'struct'))))
// Hide warnings if there are errors
return errors.find(e => e.severity === 3)
? errors.filter(e => e.severity === 3)
: errors
}, [type, node, ctx])
return <>
{errors.map(e => )}
>
}
interface ErrorIndicatorProps {
error: core.LanguageError
}
function ErrorIndicator({ error }: ErrorIndicatorProps) {
const [active, setActive] = useFocus()
return setActive()}>
{Octicon.issue_opened}
}
interface DocsProps {
desc: string | undefined
}
function Docs({ desc }: DocsProps) {
if (!desc || desc.length === 0) {
return <>>
}
const [active, setActive] = useFocus()
return setActive()}>
{Octicon.info}
}
function useToggles() {
const [toggleState, setToggleState] = useState(new Map())
const [toggleAll, setToggleAll] = useState(undefined)
const expand = useCallback((key: string) => (evt: MouseEvent) => {
if (evt.ctrlKey) {
setToggleState(new Map())
setToggleAll(true)
} else {
setToggleState(state => new Map(state.set(key, true)))
}
}, [])
const collapse = useCallback((key: string) => (evt: MouseEvent) => {
if (evt.ctrlKey) {
setToggleState(new Map())
setToggleAll(false)
} else {
setToggleState(state => new Map(state.set(key, false)))
}
}, [])
const isToggled = useCallback((key: string) => {
if (!(toggleState instanceof Map)) return false
return toggleState.get(key) ?? toggleAll
}, [toggleState, toggleAll])
return { expand, collapse, isToggled }
}