diff --git a/package-lock.json b/package-lock.json
index a3105a5f..72caf62f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31,7 +31,8 @@
"lz-string": "^1.4.4",
"marked": "^4.0.10",
"rfdc": "^1.3.0",
- "sourcemapped-stacktrace": "^1.1.11"
+ "sourcemapped-stacktrace": "^1.1.11",
+ "vscode-languageserver-textdocument": "^1.0.12"
},
"devDependencies": {
"@preact/preset-vite": "^2.4.0",
diff --git a/package.json b/package.json
index bfda0f0b..48bebef0 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
"lz-string": "^1.4.4",
"marked": "^4.0.10",
"rfdc": "^1.3.0",
- "sourcemapped-stacktrace": "^1.1.11"
+ "sourcemapped-stacktrace": "^1.1.11",
+ "vscode-languageserver-textdocument": "^1.0.12"
},
"devDependencies": {
"@preact/preset-vite": "^2.4.0",
diff --git a/src/app/components/generator/McdocRenderer.tsx b/src/app/components/generator/McdocRenderer.tsx
index 9b572007..58a4ab5b 100644
--- a/src/app/components/generator/McdocRenderer.tsx
+++ b/src/app/components/generator/McdocRenderer.tsx
@@ -63,7 +63,6 @@ function Head({ simpleType, optional, node }: HeadProps) {
}
if (type.kind === 'struct' && optional) {
- console.log(type, node)
if (node && JsonObjectNode.is(node)) {
return
}
- console.warn('Unhandled head', type)
+ // console.warn('Unhandled head', type)
return <>>
}
@@ -108,7 +107,7 @@ function Body({ simpleType, node }: BodyProps) {
if (type.kind === 'byte' || type.kind === 'short' || type.kind === 'int' || type.kind === 'boolean') {
return <>>
}
- console.warn('Unhandled body', type, node)
+ // console.warn('Unhandled body', type, node)
return <>>
}
@@ -124,8 +123,8 @@ function StructBody({ type, node }: StructBodyProps) {
const dynamicFields = type.fields.filter(field =>
field.key.kind === 'string')
if (type.fields.length !== staticFields.length + dynamicFields.length) {
- console.warn('Missed struct fields', type.fields.filter(field =>
- !staticFields.includes(field) && !dynamicFields.includes(field)))
+ // console.warn('Missed struct fields', type.fields.filter(field =>
+ // !staticFields.includes(field) && !dynamicFields.includes(field)))
}
return <>
{staticFields.map(field => {
diff --git a/src/app/components/generator/SchemaGenerator.tsx b/src/app/components/generator/SchemaGenerator.tsx
index 2c538470..a4a7bbac 100644
--- a/src/app/components/generator/SchemaGenerator.tsx
+++ b/src/app/components/generator/SchemaGenerator.tsx
@@ -82,8 +82,11 @@ export function SchemaGenerator({ gen, allowedVersions }: Props) {
ignoreChange.current = true
data = file.data
}
+ if (data) {
+ await spyglass.writeFile(version, uri, JSON.stringify(data))
+ }
// TODO: if data is undefined, set to generator's default
- const docAndNode = await spyglass.setFileContents(version, uri, data ? JSON.stringify(data) : undefined)
+ const docAndNode = await spyglass.getFile(version, uri, () => '{}')
Analytics.setGenerator(gen.id)
return docAndNode
}, [gen.id, version, sharedSnippetId, currentPreset, project.name, file?.id, spyglass])
diff --git a/src/app/components/generator/SourcePanel.tsx b/src/app/components/generator/SourcePanel.tsx
index 7310c9f7..db415621 100644
--- a/src/app/components/generator/SourcePanel.tsx
+++ b/src/app/components/generator/SourcePanel.tsx
@@ -78,7 +78,7 @@ export function SourcePanel({ spyglass, docAndNode, doCopy, doDownload, doImport
if (!spyglass || !docAndNode) return
try {
const data = await parseSource(value, format)
- await spyglass.setFileContents(version, docAndNode.doc.uri, JSON.stringify(data))
+ await spyglass.writeFile(version, docAndNode.doc.uri, JSON.stringify(data))
} catch (e) {
if (e instanceof Error) {
e.message = `Error importing: ${e.message}`
diff --git a/src/app/services/Spyglass.ts b/src/app/services/Spyglass.ts
index 296c0911..47210b03 100644
--- a/src/app/services/Spyglass.ts
+++ b/src/app/services/Spyglass.ts
@@ -9,36 +9,76 @@ import { localize } from '@spyglassmc/locales'
import * as mcdoc from '@spyglassmc/mcdoc'
import * as nbt from '@spyglassmc/nbt'
import * as zip from '@zip.js/zip.js'
+import type { TextEdit } from 'vscode-languageserver-textdocument'
+import { TextDocument } from 'vscode-languageserver-textdocument'
import type { ConfigGenerator, ConfigVersion } from '../Config.js'
import siteConfig from '../Config.js'
import { computeIfAbsent, genPath } from '../Utils.js'
import { fetchBlockStates, fetchRegistries, fetchVanillaMcdoc, getVersionChecksum } from './DataFetcher.js'
import type { VersionId } from './Versions.js'
+interface DocumentData {
+ doc: TextDocument
+ undoStack: { edits: TextEdit[] }[]
+ redoStack: { edits: TextEdit[] }[]
+}
+
export class Spyglass {
+ private static readonly LOGGER: core.Logger = console
+ private static readonly EXTERNALS: core.Externals = {
+ ...BrowserExternals,
+ archive: {
+ ...BrowserExternals.archive,
+ decompressBall,
+ },
+ }
+
private readonly instances = new Map>()
+ private readonly documents = new Map()
private readonly watchers = new Map void)[]>()
- public async setFileContents(versionId: VersionId, uri: string, contents?: string) {
- const service = await this.getService(versionId)
- if (contents !== undefined) {
- await service.project.externals.fs.writeFile(uri, contents)
- } else {
+ public async getFile(version: VersionId, uri: string, emptyContent?: () => string) {
+ const service = await this.getService(version)
+ const document = this.documents.get(uri)
+ let docAndNode: core.DocAndNode | undefined
+ if (document === undefined) {
+ let doc: TextDocument
try {
- const buffer = await service.project.externals.fs.readFile(uri)
- contents = new TextDecoder().decode(buffer)
+ const buffer = await Spyglass.EXTERNALS.fs.readFile(uri)
+ const content = new TextDecoder().decode(buffer)
+ doc = TextDocument.create(uri, 'json', 1, content)
+ Spyglass.LOGGER.info(`[Spyglass#openFile] Opening file with content from fs: ${uri}`)
} catch (e) {
- contents = '{}'
+ doc = TextDocument.create(uri, 'json', 1, emptyContent ? emptyContent() : '')
+ Spyglass.LOGGER.info(`[Spyglass#openFile] Opening empty file: ${uri}`)
}
+ this.documents.set(uri, { doc, undoStack: [], redoStack: [] })
+ await service.project.onDidOpen(doc.uri, doc.languageId, doc.version, doc.getText())
+ docAndNode = await service.project.ensureClientManagedChecked(uri)
+ } else {
+ docAndNode = service.project.getClientManaged(uri)
+ Spyglass.LOGGER.info(`[Spyglass#openFile] Opening already open file: ${uri}`)
}
- await service.project.onDidOpen(uri, 'json', 1, contents)
- const docAndNode = await service.project.ensureClientManagedChecked(uri)
if (!docAndNode) {
- throw new Error('[Spyglass setFileContents] Cannot get doc and node')
+ throw new Error(`[Spyglass#openFile] Cannot get doc and node: ${uri}`)
}
return docAndNode
}
+ public async writeFile(versionId: VersionId, uri: string, content: string) {
+ await Spyglass.EXTERNALS.fs.writeFile(uri, content)
+ Spyglass.LOGGER.info(`[Spyglass#writeFile] Writing file: ${uri} ${content.substring(0, 50)}`)
+ const doc = this.documents.get(uri)?.doc
+ if (doc !== undefined) {
+ const service = await this.getService(versionId)
+ await service.project.onDidChange(doc.uri, [{ text: content }], doc.version + 1)
+ const docAndNode = service.project.getClientManaged(doc.uri)
+ if (docAndNode) {
+ service.project.emit('documentUpdated', docAndNode)
+ }
+ }
+ }
+
public getFileContents(_uri: string): string | undefined {
return undefined // TODO
}
@@ -69,21 +109,15 @@ export class Spyglass {
const promise = (async () => {
const version = siteConfig.versions.find(v => v.id === versionId)!
const service = new core.Service({
- logger: console,
- profilers: new core.ProfilerFactory(console, [
+ logger: Spyglass.LOGGER,
+ profilers: new core.ProfilerFactory(Spyglass.LOGGER, [
'project#init',
'project#ready',
]),
project: {
cacheRoot: 'file:///cache/',
projectRoots: ['file:///project/'],
- externals: {
- ...BrowserExternals,
- archive: {
- ...BrowserExternals.archive,
- decompressBall,
- },
- },
+ externals: Spyglass.EXTERNALS,
defaultConfig: core.ConfigService.merge(core.VanillaConfig, {
env: {
gameVersion: version.ref ?? version.id,
@@ -108,7 +142,7 @@ export class Spyglass {
}
}
-const decompressBall: core.Externals['archive']['decompressBall'] = async (buffer, options) => {
+async function decompressBall(buffer: Uint8Array, options?: { stripLevel?: number }): Promise {
const reader = new zip.ZipReader(new zip.BlobReader(new Blob([buffer])))
const entries = await reader.getEntries()
return await Promise.all(entries.map(async e => {