Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/host/BinarySlddEditorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
// that a dictionary this host could not read is not passed on as an empty one, which
// the reader itself no longer enforces (it recovers and warns instead).
import { readSlddParts } from './slddContent.js';
import { isZipBytes } from './slddFormat.js';
import { sourceWarnings, warningBanner } from './parseWarnings.js';
import {
findOwningEntry,
Expand Down Expand Up @@ -90,6 +91,12 @@ import { wireNavigateSelect, drainNavigateSelect } from './navigate.js';
import { binaryEditSrcId } from '../common/srcId.js';
import type { TableToHostMessage } from '../common/protocol.js';

// viewType of the default byte-backed view (BinaryEditorProvider), which owns the rule
// that picks an editor from a .sldd's bytes. A constant here rather than an import,
// because importing that provider would be circular — the same reason it spells this
// provider's viewType out as a constant of its own.
const BINARY_VIEW_TYPE = 'dataExplorer.binaryView';

/**
* One open view of a document: the two ways the document can ask it to repaint.
*
Expand Down Expand Up @@ -183,13 +190,23 @@ class BinarySlddDocument implements vscode.CustomDocument {
*/
baselineCaptured = false;

/**
* Whether the bytes behind this document are not a zip, so this editor is the wrong one
* for them — see the misroute guard in `openCustomDocument`. Such a document holds no
* content at all; `resolveCustomEditor` hands the file back to the default view and
* disposes its panel before anything reads `chunkXml`.
*/
readonly misrouted: boolean;

constructor(
public readonly uri: vscode.Uri,
chunkXml: string,
zipMeta: Record<string, Uint8Array>,
misrouted = false,
) {
this.chunkXml = chunkXml;
this.zipMeta = zipMeta;
this.misrouted = misrouted;
}

// Prefixed, so this editable model never collides with the read-only
Expand Down Expand Up @@ -331,6 +348,22 @@ export class BinarySlddEditorProvider implements vscode.CustomEditorProvider<Bin
): Promise<BinarySlddDocument> {
const source = openContext.backupId ? vscode.Uri.parse(openContext.backupId) : uri;
const bytes = await vscode.workspace.fs.readFile(source);
// A custom editor's selector is a filename glob, and the two .sldd formats are not
// distinguishable by name — so "Reopen Editor With…" offers THIS view for every
// *.sldd, including a JSON one, which has nothing for `unzipSync` to read. That
// unzip used to throw here, before any panel existed, which the user saw as
// "invalid zip data" on a file the default view opens perfectly well.
//
// Answer it the way the default view answers the mirror case, by handing the file
// back rather than by classifying it here: `resolveCustomEditor` reopens it with
// `dataExplorer.binaryView`, which owns the one format-to-editor rule. Deciding
// "JSON ⇒ the table view" locally would be that rule's second copy, and would get
// the over-the-sync-limit dictionary wrong — the default view keeps that one
// read-only, because the text-backed table cannot resolve it at all.
//
// Restoring a hot-exit backup reaches here too, and `writeTo` always writes a zip,
// so a backup takes the normal path.
if (!isZipBytes(bytes)) return new BinarySlddDocument(uri, '', {}, true);
const zip = unzipSync(bytes);
const chunk = zip[DATA_PART_XML];
if (!chunk) throw new Error(`Missing ${DATA_PART_XML} in binary SLDD`);
Expand All @@ -346,6 +379,15 @@ export class BinarySlddEditorProvider implements vscode.CustomEditorProvider<Bin
webviewPanel: vscode.WebviewPanel,
_token: vscode.CancellationToken,
): Promise<void> {
// Not a zip, so not ours — see the misroute guard in openCustomDocument. Reopen with
// the default byte-backed view, which routes on the bytes, and dispose THIS panel so
// only the tab that cannot render the file goes away.
if (document.misrouted) {
await vscode.commands.executeCommand('vscode.openWith', document.uri, BINARY_VIEW_TYPE);
webviewPanel.dispose();
return;
}

const webview = webviewPanel.webview;
const distRoot = vscode.Uri.joinPath(this.context.extensionUri, 'dist', 'webview');
webview.options = { enableScripts: true, localResourceRoots: [distRoot] };
Expand Down
40 changes: 40 additions & 0 deletions test-integration/suite/binarySlddEdit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,44 @@ suite('BinarySlddEditorProvider', () => {
doc.dispose();
await vscode.workspace.fs.delete(dst);
});

// "Reopen Editor With…" offers this viewType for EVERY *.sldd, because a selector
// is a filename glob and the two .sldd formats are not distinguishable by name. So
// the user can land here with a JSON dictionary, which is not a zip and has nothing
// for `unzipSync` to read. Opening it must route to the editor that fits the bytes,
// the way an Explorer double-click already does — not throw at the user.
test('a JSON-text .sldd opened here routes to the view that fits its bytes', async () => {
const uri = wsUri('data.sldd');
// The failure this pins is in openCustomDocument, BEFORE any panel exists: the
// unconditional unzip threw "invalid zip data", which VS Code surfaces as a
// notification and an empty tab.
const doc = await provider.openCustomDocument(uri, {} as vscode.CustomDocumentOpenContext, token());
const panel = makePanel();
let disposed = false;
panel.onDidDispose(() => (disposed = true));

await provider.resolveCustomEditor(doc, panel, token());

assert.ok(disposed, 'the panel for the wrong-format document disposes itself');

// Routed to the DEFAULT byte-backed view, which owns the one format-to-editor
// rule — it then lands an editable JSON .sldd in the table view (and keeps an
// over-the-sync-limit one read-only, which is why the choice is not made here).
// So the tab the user ends up looking at is the table view.
const start = Date.now();
let found = false;
while (Date.now() - start < 5000) {
found = vscode.window.tabGroups.all
.flatMap((g) => g.tabs)
.some(
(t) =>
(t.input as { viewType?: string })?.viewType === 'dataExplorer.tableView' &&
(t.input as { uri?: vscode.Uri })?.uri?.toString() === uri.toString(),
);
if (found) break;
await new Promise((r) => setTimeout(r, 50));
}
assert.ok(found, 'the JSON .sldd ended up in the editable table view');
doc.dispose();
});
});
Loading