mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-24 07:37:10 +00:00
Refactor makeEdit so it returns the result node
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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>(() => {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user