Refactor makeEdit so it returns the result node

This commit is contained in:
Misode
2024-10-23 21:41:12 +02:00
parent 6151fbcea4
commit 6e68de01aa
5 changed files with 136 additions and 103 deletions

View File

@@ -8,7 +8,6 @@ import type { SimplifiedStructType } from '@spyglassmc/mcdoc/lib/runtime/checker
import { useCallback } from 'preact/hooks'
import type { TextDocument } from 'vscode-languageserver-textdocument'
import { useLocale } from '../../contexts/Locale.jsx'
import type { AstEdit } from '../../services/Spyglass.js'
import { Octicon } from '../Octicon.jsx'
export interface McdocContext {
@@ -16,9 +15,11 @@ export interface McdocContext {
symbols: core.SymbolUtil,
}
type MakeEdit = (edit: (range: core.Range) => JsonNode | undefined) => void
interface Props {
node: JsonNode | undefined
makeEdit: (edit: AstEdit) => void
makeEdit: MakeEdit
ctx: McdocContext
}
export function McdocRoot({ node, makeEdit, ctx } : Props) {
@@ -90,18 +91,17 @@ function StringHead({ node, makeEdit }: Props) {
const value = JsonStringNode.is(node) ? node.value : undefined
const onChangeValue = useCallback((value: string) => {
makeEdit(() => {
replaceNode(node, (range, parent) => {
const newNode: JsonStringNode = {
type: 'json:string',
range,
options: json.parser.JsonStringOptions,
value: value,
valueMap: [{ inner: core.Range.create(0), outer: core.Range.create(range.start) }],
parent,
}
return newNode
})
makeEdit((range) => {
if (value.length === 0) {
return undefined
}
return {
type: 'json:string',
range,
options: json.parser.JsonStringOptions,
value: value,
valueMap: [{ inner: core.Range.create(0), outer: core.Range.create(range.start) }],
}
})
}, [node, makeEdit])
@@ -121,25 +121,21 @@ function NumericHead({ type, node, makeEdit }: NumericHeadProps) {
if (number !== undefined && Number.isNaN(number)) {
return
}
makeEdit(() => {
makeEdit((range) => {
if (number === undefined) {
removeNode(node)
return
return undefined
}
replaceNode(node, (range, parent) => {
const newValue: core.FloatNode | core.LongNode = isFloat
? { type: 'float', range, value: number }
: { type: 'long', range, value: BigInt(number) }
const newNode: JsonNumberNode = {
type: 'json:number',
range,
value: newValue,
children: [newValue],
parent,
}
newValue.parent = newNode
return newNode
})
const newValue: core.FloatNode | core.LongNode = isFloat
? { type: 'float', range, value: number }
: { type: 'long', range, value: BigInt(number) }
const newNode: JsonNumberNode = {
type: 'json:number',
range,
value: newValue,
children: [newValue],
}
newValue.parent = newNode
return newNode
})
}, [isFloat, node, makeEdit])
@@ -150,19 +146,14 @@ function BooleanHead({ node, makeEdit }: Props) {
const value = node && JsonBooleanNode.is(node) ? node.value : undefined
const onSelect = useCallback((newValue: boolean) => {
makeEdit(() => {
makeEdit((range) => {
if (value === newValue) {
removeNode(node)
} else {
replaceNode(node, (range, parent) => {
const newNode: JsonBooleanNode = {
type: 'json:boolean',
range,
value: newValue,
parent,
}
return newNode
})
return undefined
}
return {
type: 'json:boolean',
range,
value: newValue,
}
})
}, [node, makeEdit, value])
@@ -188,23 +179,32 @@ function ListHead({ type, node, makeEdit, ctx }: ListHeadProps) {
const onAdd = useCallback(() => {
if (canAdd) {
makeEdit(() => {
makeEdit((range) => {
const itemType: McdocType = type.kind === 'list' ? type.item
: type.kind === 'byte_array' ? { kind: 'byte' }
: type.kind === 'int_array' ? { kind: 'int' }
: type.kind === 'long_array' ? { kind: 'long' }
: { kind: 'any' }
addNode(node, (range, parent) => {
const newValue = getDefault(itemType, range, ctx)
const newNode: core.ItemNode<JsonNode> = {
type: 'item',
range,
children: [newValue],
value: newValue,
parent,
}
return newNode
})
const newValue = getDefault(itemType, range, ctx)
const newItem: core.ItemNode<JsonNode> = {
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, canAdd])
@@ -260,14 +260,53 @@ function StructBody({ type, node, makeEdit, ctx }: StructBodyProps) {
}
return <>
{staticFields.map(field => {
const key = (field.key as LiteralType).value.value
const child = node.children.find(p => p.key?.value === key)?.value
const key = (field.key as LiteralType).value.value.toString()
const childIndex = node.children.findIndex(p => p.key?.value === key)
const child = childIndex === -1 ? undefined : node.children[childIndex]
const childValue = child?.value
const makeFieldEdit: MakeEdit = (edit) => {
if (child) {
makeEdit(() => {
const newChild = edit(childValue?.range ?? core.Range.create(child.range.end))
if (newChild === undefined) {
node.children.splice(childIndex, 1)
} else {
node.children[childIndex] = {
type: 'pair',
range: child.range,
key: child.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 <div class="node">
<div class="node-header">
<Key label={key} />
<Head simpleType={field.type} node={child} optional={field.optional} makeEdit={makeEdit} ctx={ctx} />
<Head simpleType={field.type} node={childValue} optional={field.optional} makeEdit={makeFieldEdit} ctx={ctx} />
</div>
<Body simpleType={field.type} node={child} makeEdit={makeEdit} ctx={ctx} />
<Body simpleType={field.type} node={childValue} makeEdit={makeFieldEdit} ctx={ctx} />
</div>
})}
</>
@@ -290,11 +329,23 @@ function ListBody({ type, node, makeEdit, ctx }: ListBodyProps) {
const onRemoveItem = useCallback((index: number) => {
makeEdit(() => {
node.children.splice(index, 1)
return node
})
}, [makeEdit, node])
return <>
{node.children.map((item, index) => {
const child = item.value
const makeItemEdit: MakeEdit = (edit) => {
makeEdit(() => {
const newChild = edit(child?.range ?? item.range)
node.children[index] = {
type: 'item',
range: item.range,
value: newChild,
}
return node
})
}
return <div class="node">
<div class="node-header">
<button class="remove tooltipped tip-se" aria-label={locale('remove')} onClick={() => onRemoveItem(index)}>
@@ -309,46 +360,14 @@ function ListBody({ type, node, makeEdit, ctx }: ListBodyProps) {
</button>
</div>}
<Key label="entry" />
<Head simpleType={type.item} node={child} makeEdit={makeEdit} ctx={ctx} />
<Head simpleType={type.item} node={child} makeEdit={makeItemEdit} ctx={ctx} />
</div>
<Body simpleType={type.item} node={child} makeEdit={makeEdit} ctx={ctx} />
<Body simpleType={type.item} node={child} makeEdit={makeItemEdit} ctx={ctx} />
</div>
})}
</>
}
function addNode<T extends core.AstNode>(parent: T | undefined, getNewNode: (range: core.Range, parent: T) => core.AstNode) {
if (parent?.children !== undefined) {
const range = core.Range.create(parent.range.end)
const newNode = getNewNode(range, parent)
parent.children.push(newNode)
}
}
function replaceNode<T extends core.AstNode>(node: T | undefined, getNewNode: (range: core.Range, parent: core.AstNode) => T) {
if (node !== undefined && node.parent?.children) {
const index = node.parent.children.findIndex(c => c === node)
if (index !== -1) {
const newNode = getNewNode(node.range, node.parent)
node.parent.children[index] = newNode
if (core.PairNode.is(node.parent)) {
;(node.parent as core.Mutable<core.PairNode<core.AstNode, core.AstNode>>).value = newNode
}
}
}
}
function removeNode<T extends core.AstNode>(node: T | undefined) {
if (node !== undefined && node.parent?.children) {
if (core.PairNode.is(node.parent)) {
removeNode(node.parent)
} else {
const index = node.parent.children.findIndex(c => c === node)
node.parent.children.splice(index, 1)
}
}
}
function getDefault(type: McdocType, range: core.Range, ctx: McdocContext): JsonNode {
if (type.kind === 'string') {
return JsonStringNode.mock(range)

View File

@@ -1,9 +1,9 @@
import type { DocAndNode } from '@spyglassmc/core'
import type { DocAndNode, Range } from '@spyglassmc/core'
import type { JsonNode } from '@spyglassmc/json'
import { JsonFileNode } from '@spyglassmc/json'
import { useCallback, useErrorBoundary, useMemo } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useDocAndNode, useSpyglass } from '../../contexts/Spyglass.jsx'
import type { AstEdit } from '../../services/Spyglass.js'
import type { McdocContext } from './McdocRenderer.jsx'
import { McdocRoot } from './McdocRenderer.jsx'
@@ -28,11 +28,21 @@ export function Tree({ docAndNode, onError }: TreePanelProps) {
})
if (error) return <></>
const makeEdit = useCallback((edit: AstEdit) => {
const makeEdit = useCallback((edit: (range: Range) => JsonNode | undefined) => {
if (!service) {
return
}
service.applyEdit(docAndNode.doc.uri, edit)
service.applyEdit(docAndNode.doc.uri, (fileNode) => {
const jsonFile = fileNode.children[0]
if (JsonFileNode.is(jsonFile)) {
const original = jsonFile.children[0]
const newNode = edit(original.range)
if (newNode !== undefined) {
newNode.parent = fileNode
fileNode.children[0] = newNode
}
}
})
}, [service, docAndNode])
const ctx = useMemo<McdocContext | undefined>(() => {

View File

@@ -122,8 +122,11 @@ function shuffle<T>(array: T[], ctx: LootContext) {
}
function generateTable(table: any, consumer: ItemConsumer, ctx: LootContext) {
if (!Array.isArray(table.pools)) {
return
}
const tableConsumer = decorateFunctions(table.functions ?? [], consumer, ctx)
for (const pool of table.pools ?? []) {
for (const pool of table.pools) {
generatePool(pool, tableConsumer, ctx)
}
}

View File

@@ -117,8 +117,11 @@ function shuffle<T>(array: T[], ctx: LootContext) {
}
function generateTable(table: any, consumer: ItemConsumer, ctx: LootContext) {
if (!Array.isArray(table.pools)) {
return
}
const tableConsumer = decorateFunctions(table.functions ?? [], consumer, ctx)
for (const pool of table.pools ?? []) {
for (const pool of table.pools) {
generatePool(pool, tableConsumer, ctx)
}
}

View File

@@ -18,8 +18,6 @@ import { computeIfAbsent, genPath } from '../Utils.js'
import { fetchBlockStates, fetchRegistries, fetchVanillaMcdoc, getVersionChecksum } from './DataFetcher.js'
import type { VersionId } from './Versions.js'
export type AstEdit = (docAndNode: core.DocAndNode) => void
interface ClientDocument {
doc: TextDocument
undoStack: string[]
@@ -111,7 +109,7 @@ export class SpyglassService {
}
}
public async applyEdit(uri: string, edit: AstEdit) {
public async applyEdit(uri: string, edit: (node: core.FileNode<core.AstNode>) => void) {
const document = this.client.documents.get(uri)
if (document !== undefined) {
document.undoStack.push(document.doc.getText())
@@ -120,7 +118,7 @@ export class SpyglassService {
if (!docAndNode) {
throw new Error(`[Spyglass#openFile] Cannot get doc and node: ${uri}`)
}
edit(docAndNode)
edit(docAndNode.node)
const newText = this.service.format(docAndNode.node, docAndNode.doc, 2, true)
TextDocument.update(document.doc, [{ text: newText }], document.doc.version + 1)
await this.service.project.externals.fs.writeFile(uri, document.doc.getText())