Make edits to AST and then use formatter

This commit is contained in:
Misode
2024-10-23 06:10:14 +02:00
parent a0f3e71000
commit 18332b9dbc
5 changed files with 47 additions and 45 deletions

View File

@@ -2,26 +2,27 @@ import type { JsonNode } from '@spyglassmc/json'
import { JsonArrayNode, JsonBooleanNode, JsonNumberNode, JsonObjectNode, JsonStringNode } from '@spyglassmc/json'
import type { ListType, LiteralType, McdocType } from '@spyglassmc/mcdoc'
import type { SimplifiedStructType } from '@spyglassmc/mcdoc/lib/runtime/checker/index.js'
import { useCallback } from 'preact/hooks'
import { useLocale } from '../../contexts/Locale.jsx'
import type { Edit } from '../../services/Spyglass.js'
import type { AstEdit } from '../../services/Spyglass.js'
import { Octicon } from '../Octicon.jsx'
interface Props {
node: JsonNode | undefined
makeEdits: (edits: Edit[]) => void
makeEdit: (edit: AstEdit) => void
}
export function McdocRoot({ node, makeEdits } : Props) {
export function McdocRoot({ node, makeEdit } : Props) {
const type = node?.typeDef ?? { kind: 'unsafe' }
if (type.kind === 'struct') {
return <StructBody type={type} node={node} makeEdits={makeEdits} />
return <StructBody type={type} node={node} makeEdit={makeEdit} />
}
return <>
<div class="node-header">
<Head simpleType={type} node={node} makeEdits={makeEdits} />
<Head simpleType={type} node={node} makeEdit={makeEdit} />
</div>
<Body simpleType={type} node={node} makeEdits={makeEdits} />
<Body simpleType={type} node={node} makeEdit={makeEdit} />
</>
}
@@ -91,14 +92,14 @@ function Head({ simpleType, optional, node }: HeadProps) {
interface BodyProps extends Props {
simpleType: McdocType
}
function Body({ simpleType, node, makeEdits }: BodyProps) {
function Body({ simpleType, node, makeEdit }: BodyProps) {
const type = node?.typeDef ?? simpleType
if (node?.typeDef?.kind === 'struct') {
if (node.typeDef.fields.length === 0) {
return <></>
}
return <div class="node-body">
<StructBody type={node.typeDef} node={node} makeEdits={makeEdits} />
<StructBody type={node.typeDef} node={node} makeEdit={makeEdit} />
</div>
}
if (node?.typeDef?.kind === 'list') {
@@ -107,7 +108,7 @@ function Body({ simpleType, node, makeEdits }: BodyProps) {
return <></>
}
return <div class="node-body">
<ListBody type={node.typeDef} node={node} makeEdits={makeEdits} />
<ListBody type={node.typeDef} node={node} makeEdit={makeEdit} />
</div>
}
if (type.kind === 'byte' || type.kind === 'short' || type.kind === 'int' || type.kind === 'boolean') {
@@ -120,7 +121,7 @@ function Body({ simpleType, node, makeEdits }: BodyProps) {
interface StructBodyProps extends Props {
type: SimplifiedStructType
}
function StructBody({ type, node, makeEdits }: StructBodyProps) {
function StructBody({ type, node, makeEdit }: StructBodyProps) {
if (!JsonObjectNode.is(node)) {
return <></>
}
@@ -139,9 +140,9 @@ function StructBody({ type, node, makeEdits }: StructBodyProps) {
return <div class="node">
<div class="node-header">
<Key label={key} />
<Head simpleType={field.type} node={child} optional={field.optional} makeEdits={makeEdits} />
<Head simpleType={field.type} node={child} optional={field.optional} makeEdit={makeEdit} />
</div>
<Body simpleType={field.type} node={child} makeEdits={makeEdits} />
<Body simpleType={field.type} node={child} makeEdit={makeEdit} />
</div>
})}
</>
@@ -156,17 +157,22 @@ function Key({ label }: { label: string | number | boolean }) {
interface ListBodyProps extends Props {
type: ListType
}
function ListBody({ type, node, makeEdits }: ListBodyProps) {
function ListBody({ type, node, makeEdit }: ListBodyProps) {
const { locale } = useLocale()
if (!JsonArrayNode.is(node)) {
return <></>
}
const onRemoveItem = useCallback((index: number) => {
makeEdit(() => {
node.children.splice(index, 1)
})
}, [makeEdit, node])
return <>
{node.children.map((item, index) => {
const child = item.value
return <div class="node">
<div class="node-header">
<button class="remove tooltipped tip-se" aria-label={locale('remove')} onClick={() => makeEdits([{ range: item.range, text: '' }])}>
<button class="remove tooltipped tip-se" aria-label={locale('remove')} onClick={() => onRemoveItem(index)}>
{Octicon.trashcan}
</button>
{node.children.length > 1 && <div class="node-move">
@@ -178,9 +184,9 @@ function ListBody({ type, node, makeEdits }: ListBodyProps) {
</button>
</div>}
<Key label="entry" />
<Head simpleType={type.item} node={child} makeEdits={makeEdits} />
<Head simpleType={type.item} node={child} makeEdit={makeEdit} />
</div>
<Body simpleType={type.item} node={child} makeEdits={makeEdits} />
<Body simpleType={type.item} node={child} makeEdit={makeEdit} />
</div>
})}
</>

View File

@@ -119,7 +119,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
return
}
Analytics.undoGenerator(gen.id, 1, 'menu')
await service.undoEdits(uri)
await service.undoEdit(uri)
}
const redo = async (e: MouseEvent) => {
e.stopPropagation()
@@ -127,7 +127,7 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
return
}
Analytics.redoGenerator(gen.id, 1, 'menu')
await service?.redoEdits(uri)
await service?.redoEdit(uri)
}
useEffect(() => {
@@ -137,10 +137,10 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
}
if (e.ctrlKey && e.key === 'z') {
Analytics.undoGenerator(gen.id, 1, 'hotkey')
await service.undoEdits(uri)
await service.undoEdit(uri)
} else if (e.ctrlKey && e.key === 'y') {
Analytics.redoGenerator(gen.id, 1, 'hotkey')
await service.redoEdits(uri)
await service.redoEdit(uri)
}
}
const onKeyDown = (e: KeyboardEvent) => {

View File

@@ -3,7 +3,7 @@ import { JsonFileNode } from '@spyglassmc/json'
import { useCallback, useErrorBoundary } from 'preact/hooks'
import { useLocale } from '../../contexts/index.js'
import { useDocAndNode, useSpyglass } from '../../contexts/Spyglass.jsx'
import type { Edit } from '../../services/Spyglass.js'
import type { AstEdit } from '../../services/Spyglass.js'
import { McdocRoot } from './McdocRenderer.jsx'
type TreePanelProps = {
@@ -27,14 +27,14 @@ export function Tree({ docAndNode, onError }: TreePanelProps) {
})
if (error) return <></>
const makeEdits = useCallback((edits: Edit[]) => {
const makeEdit = useCallback((edit: AstEdit) => {
if (!service) {
return
}
service.applyEdits(docAndNode.doc.uri, edits)
service.applyEdit(docAndNode.doc.uri, edit)
}, [service, docAndNode])
return <div class="tree node-root" data-cy="tree">
<McdocRoot node={fileChild.children[0]} makeEdits={makeEdits} />
<McdocRoot node={fileChild.children[0]} makeEdit={makeEdit} />
</div>
}

View File

@@ -59,18 +59,10 @@ export function SpyglassProvider({ children }: { children: ComponentChildren })
const { version } = useVersion()
const [client] = useState(new SpyglassClient())
const { value: service, error } = useAsync(() => {
const { value: service } = useAsync(() => {
return client.createService(version)
}, [client, version])
useEffect(() => {
if (error) {
console.warn(error)
}
}, [error])
console.log('->', service)
const value: SpyglassContext = {
client,
service,

View File

@@ -4,6 +4,7 @@ import type { McmetaSummary } from '@spyglassmc/java-edition/lib/dependency/inde
import { Fluids, ReleaseVersion, symbolRegistrar } from '@spyglassmc/java-edition/lib/dependency/index.js'
import * as jeJson from '@spyglassmc/java-edition/lib/json/index.js'
import * as jeMcf from '@spyglassmc/java-edition/lib/mcfunction/index.js'
import type { JsonFileNode } from '@spyglassmc/json'
import * as json from '@spyglassmc/json'
import { localize } from '@spyglassmc/locales'
import * as mcdoc from '@spyglassmc/mcdoc'
@@ -17,10 +18,7 @@ import { computeIfAbsent, genPath } from '../Utils.js'
import { fetchBlockStates, fetchRegistries, fetchVanillaMcdoc, getVersionChecksum } from './DataFetcher.js'
import type { VersionId } from './Versions.js'
export interface Edit {
range?: core.Range
text: string
}
export type AstEdit = (docAndNode: core.DocAndNode) => void
interface ClientDocument {
doc: TextDocument
@@ -63,7 +61,6 @@ export class SpyglassService {
public async getFile(uri: string, emptyContent?: () => string) {
let docAndNode = this.service.project.getClientManaged(uri)
if (docAndNode === undefined) {
console.info(`[Spyglass#openFile] Opening file with content from fs: ${uri}`)
const content = await this.readFile(uri)
const doc = TextDocument.create(uri, 'json', 1, content ?? (emptyContent ? emptyContent() : ''))
await this.service.project.onDidOpen(doc.uri, doc.languageId, doc.version, doc.getText())
@@ -110,21 +107,24 @@ export class SpyglassService {
}
}
public async applyEdits(uri: string, edits: Edit[]) {
public async applyEdit(uri: string, edit: AstEdit) {
const document = this.client.documents.get(uri)
if (document !== undefined) {
document.undoStack.push(document.doc.getText())
document.redoStack = []
TextDocument.update(document.doc, edits.map(e => ({
range: e.range ? getLsRange(e.range, document.doc) : undefined,
text: e.text,
})), document.doc.version + 1)
const docAndNode = this.service.project.getClientManaged(uri)
if (!docAndNode) {
throw new Error(`[Spyglass#openFile] Cannot get doc and node: ${uri}`)
}
edit(docAndNode)
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())
await this.notifyChange(document.doc)
}
}
public async undoEdits(uri: string) {
public async undoEdit(uri: string) {
const document = this.client.documents.get(uri)
if (document === undefined) {
throw new Error(`[Spyglass#undoEdits] Document doesn't exist: ${uri}`)
@@ -139,7 +139,7 @@ export class SpyglassService {
await this.notifyChange(document.doc)
}
public async redoEdits(uri: string) {
public async redoEdit(uri: string) {
const document = this.client.documents.get(uri)
if (document === undefined) {
throw new Error(`[Spyglass#redoEdits] Document doesn't exist: ${uri}`)
@@ -250,6 +250,10 @@ const initialize: core.ProjectInitializer = async (ctx) => {
jeMcf.initialize(ctx, summary.commands, release)
nbt.initialize(ctx)
meta.registerFormatter<JsonFileNode>('json:file', (node, ctx) => {
return ctx.meta.getFormatter(node.children[0].type)(node.children[0], ctx)
})
return { loadedVersion: release }
}