From ea37eb168ffe89932bed70922669d163a8841075 Mon Sep 17 00:00:00 2001 From: Misode Date: Tue, 22 Oct 2024 15:43:02 +0200 Subject: [PATCH] Keep track of opened documents and prepare for undo/redo --- package-lock.json | 3 +- package.json | 3 +- .../components/generator/McdocRenderer.tsx | 9 +-- .../components/generator/SchemaGenerator.tsx | 5 +- src/app/components/generator/SourcePanel.tsx | 2 +- src/app/services/Spyglass.ts | 76 ++++++++++++++----- 6 files changed, 68 insertions(+), 30 deletions(-) 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 => {