improvement: Read zip inside the plugin - #2073
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe extension adds a read-only ChangesJAR filesystem support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant VSCode
participant Extension
participant LanguageClient
participant JarFileSystemProvider
participant yauzl
VSCode->>Extension: activate onFileSystem:jar-fs
Extension->>JarFileSystemProvider: register read-only provider
LanguageClient->>Extension: request JAR document
Extension->>JarFileSystemProvider: open translated jar-fs URI
JarFileSystemProvider->>yauzl: load archive entry
yauzl-->>JarFileSystemProvider: return entry bytes or metadata
JarFileSystemProvider-->>VSCode: return document content
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/jarFileSystemProvider.ts`:
- Around line 130-144: Update the archive indexing logic in getJarEntries so
that, for every archive entry, each missing parent path is added to the entries
map as a FileType.Directory entry. Preserve explicit archive entries and
existing stat/readDirectory behavior, allowing stat() to resolve implicit
package directories such as com/ and com/example/.
- Around line 42-55: Update translateJarToJarFs and translateJarFsToJar to
preserve the original serialized jar Uri in jarFsToJarUri instead of storing
uri.toString() after decoding. Construct the reverse jar-fs value through the
Uri API so encoded archive paths and query components serialize correctly, and
add round-trip coverage for encoded JAR paths.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6733f7c9-d08b-49a0-8862-674eb5fffab4
📒 Files selected for processing (6)
package.jsonsrc/documentSelector.tssrc/extension.tssrc/goToLocation.tssrc/jarFileSystemProvider.tssrc/test/unit/documentSelector.test.ts
| export function translateJarFsToJar(uri: Uri): string { | ||
| if (uri.scheme !== "jar-fs") { | ||
| return uri.toString(); | ||
| } | ||
|
|
||
| // First, try to reconstruct from query parameter (self-contained URI) | ||
| if (uri.query) { | ||
| const params = new URLSearchParams(uri.query); | ||
| const jarPath = params.get("jarPath"); | ||
| if (jarPath) { | ||
| // Path format: /jarName.jar/internal/path | ||
| const pathParts = uri.path.split("/").filter((p) => p); | ||
| const internalPath = pathParts.slice(1).join("/"); | ||
| return `jar:file://${jarPath}!/${internalPath}`; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repository files =="
git ls-files | sed -n '1,120p'
echo "== locate target file =="
fd -a 'jarFileSystemProvider\.ts$' . || true
echo "== target outline =="
ast-grep outline src/jarFileSystemProvider.ts || true
echo "== relevant line ranges with context =="
wc -l src/jarFileSystemProvider.ts
sed -n '1,120p' src/jarFileSystemProvider.ts
sed -n '440,530p' src/jarFileSystemProvider.tsRepository: scalameta/metals-vscode
Length of output: 10331
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package info for vscode dependency =="
node - <<'JS'
const fs = requirefs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
console.log(JSON.stringify({dependencies: pkg.dependencies && pkg.dependencies.vscode, devDependencies: pkg.devDependencies && pkg.devDependencies.vscode}));
JS
echo "== uri parser / translation usage =="
rg -n "translateJar(Fs)?To(Jar|Fs)|parseJarUri|jarFsToJarUri|JarFileSystemProvider" src test tests . --glob '!**/node_modules/**' || true
echo "== deterministic encode/decode probe =="
node - <<'JS'
const cases = [
"jar:file:///path/to/my jar.jar!/a b%c.scala",
"jar:file:///C:/Users/Foo Bar/docs%2Fthing.jar!/src%2FFile.scala",
"jar:file:///path/to/file#name.jar!/path#name.scala",
"jar:file:///path/to/file?name.jar!/query?name.scala",
];
for (const jarUri of cases) {
const decoded = decodeURIComponent(jarUri);
const sep = decoded.indexOf("!/");
const fullJarPath = decoded.substring(0, sep);
const internalPath = decoded.substring(sep + 2);
const jarName = fullJarPath.split("/").filter(Boolean).slice()
.pop();
const selfContained = `jar-fs:/${encodeURIComponent(jarName)}/${encodeURIComponent(internalPath)}?jarPath=${encodeURIComponent(fullJarPath)}`;
const parts = jarName === jarName ? [] : [];
const pathParts = selfContained.split("/").filter(Boolean).slice(-1);
const internalAttempt = pathParts.length ? pathParts.slice(1).join("/") : "";
console.log("---", jarUri);
console.log("registry decoded:", decoded);
console.log("self-contained jarPath query:", decodeURIComponent(selfContained.split("jarPath=")[1]));
console.log("current path slice attempt:", internalAttempt);
}
JSRepository: scalameta/metals-vscode
Length of output: 4582
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package info for vscode dependency =="
node - <<'JS'
const fs = requirefs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
console.log(JSON.stringify({dependencies: pkg.dependencies && pkg.dependencies.vscode, devDependencies: pkg.devDependencies && pkg.devDependencies.vscode}));
JS
echo "== deterministic encode/decode probe with error recovery =="
node - <<'JS'
const cases = [
"jar:file:///path/to/my%20jar.jar!/a%20b%25c.scala",
"jar:file:///C:/Users/Foo%20Bar/docs%252Fthing.jar!/src%252FFile.scala",
"jar:file:///path/to/file%23name.jar!/path%23name.scala",
"jar:file:///path/to/file%3Fname.jar!/query%3Fname.scala",
];
for (const jarUri of cases) {
let decoded;
try {
decoded = decodeURIComponent(jarUri);
} catch (e) {
console.log("--- malformed parse", jarUri);
console.log("decode error:", e.name + ": " + e.message);
continue;
}
const sep = decoded.indexOf("!/");
const fullJarPath = decoded.substring(0, sep);
const internalPath = decoded.substring(sep + 2);
const jarName = fullJarPath.split("/").filter(Boolean).slice().pop();
const selfContained = `jar-fs:/${encodeURIComponent(jarName)}/${encodeURIComponent(internalPath)}?jarPath=${encodeURIComponent(fullJarPath)}`;
const parsedQuery = new URLSearchParams(selfContained.split("?")[1] || "");
const parsedJarPath = parsedQuery.get("jarPath");
const pathOnly = selfContained
.split("?")[0]
.split("/")
.filter(Boolean);
const slash = pathOnly.slice(0, 1).join("/");
const sliceInternal = pathOnly.slice(1).join("/");
console.log("---", jarUri);
console.log("decoded:", decoded);
console.log("registry stored decoded:", decoded);
console.log("self-contained original:", selfContained);
console.log("self-contained query jarPath:", parsedJarPath);
console.log("path-only slash:", slash);
console.log("path-only slice/internal:", slash + "/" + sliceInternal);
}
JSRepository: scalameta/metals-vscode
Length of output: 2222
Store and return the raw serialized jar URI during the jar ↔ jar-fs round trip.
translateJarToJarFs decodes uri.toString() and stores that decoded form in jarFsToJarUri; translateJarFsToJar then returns either that decoded registry value or a reconstructed string that normalizes percent-encoded #, ?, or path characters. Keep the original Uri in the registry and build the reverse jar-fs URI with Uri so each path/query component serializes correctly. Add round-trip coverage for encoded JAR/archive paths.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/jarFileSystemProvider.ts` around lines 42 - 55, Update
translateJarToJarFs and translateJarFsToJar to preserve the original serialized
jar Uri in jarFsToJarUri instead of storing uri.toString() after decoding.
Construct the reverse jar-fs value through the Uri API so encoded archive paths
and query components serialize correctly, and add round-trip coverage for
encoded JAR paths.
| const entries = await this.getJarEntries(components.jarPath); | ||
| const entry = entries.get(components.internalPath); | ||
|
|
||
| if (!entry) { | ||
| if (components.internalPath === "" || components.internalPath === "/") { | ||
| const stat: FileStat = { | ||
| type: FileType.Directory, | ||
| ctime: 0, | ||
| mtime: 0, | ||
| size: 0, | ||
| }; | ||
| this.statCache.set(key, stat); | ||
| return stat; | ||
| } | ||
| throw FileSystemError.FileNotFound(uri); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Index implicit parent directories.
Many ZIP/JAR files contain com/example/Foo.scala without explicit com/ and com/example/ entries. readDirectory() can discover these paths by prefix, but stat() rejects them because entries.get(components.internalPath) is absent. VS Code can therefore fail to open or navigate package directories.
Add each parent path as a FileType.Directory entry while indexing every archive entry.
Proposed fix
entries.set(name, {
name: name.split("/").pop() || name,
type: isDirectory ? FileType.Directory : FileType.File,
size: entry.uncompressedSize,
});
+
+ const segments = name.split("/");
+ for (let i = 1; i < segments.length; i++) {
+ const directoryPath = segments.slice(0, i).join("/");
+ entries.set(directoryPath, {
+ name: segments[i - 1],
+ type: FileType.Directory,
+ size: 0,
+ });
+ }Also applies to: 370-382
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/jarFileSystemProvider.ts` around lines 130 - 144, Update the archive
indexing logic in getJarEntries so that, for every archive entry, each missing
parent path is added to the entries map as a FileType.Directory entry. Preserve
explicit archive entries and existing stat/readDirectory behavior, allowing
stat() to resolve implicit package directories such as com/ and com/example/.
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
Instead of reading the files in Metals and sending heavy data using LSP it's much easier to just read the jar in VS Code itself. Virtual docs are anyway only supported in VS Code, so any other editors will just get the normal filesystem breadcrumbs.
This has an added benefit of being able to quickly switch between siblings.
Screen.Recording.2026-08-06.at.19.22.53.mov
TODO: Metals focus command needs to be fixed.
Summary by CodeRabbit