Skip to content

feat: add Zed extension and make language server editor-agnostic#125

Open
withxat wants to merge 24 commits into
npmx-dev:mainfrom
withxat:feat/zed-extension
Open

feat: add Zed extension and make language server editor-agnostic#125
withxat wants to merge 24 commits into
npmx-dev:mainfrom
withxat:feat/zed-extension

Conversation

@withxat
Copy link
Copy Markdown

@withxat withxat commented Apr 27, 2026

Summary

  • Adds a Zed extension that launches the shared npmx-language-server over stdio, giving Zed users hover, version completion, diagnostics, document links, and catalog resolution.
  • Removes VS Code-specific assumptions from the language server so the same binary serves any LSP client. package-manager-detector replaces the npmx/getPackageManager request, and a new ClientFeatures channel lets each editor opt into codicons, catalog inlay hints, etc.
  • Adds a config-resolution fallback so Zed's flat lsp.npmx.settings.* shape works alongside VS Code's namespaced keys.

What changed

New: Zed extension (extensions/zed/)

  • Rust extension targeting wasm32-wasip2, built against zed_extension_api 0.7.
  • Launches node packages/language-server/dist/index.cjs --stdio by default; respects lsp.npmx.binary for full overrides, merging worktree.shell_env() with user-supplied env.
  • Forwards lsp.npmx.settings as workspace configuration under the npmx namespace.

Language server: editor-agnostic

  • Drops the npmx/getPackageManager request that depended on VS Code's npm.packageManager command. Now uses package-manager-detector, bundled into the dist.
  • Adds ClientFeatures (negotiated via initializationOptions):
    • iconStyle: 'codicon' | 'emoji' — VS Code uses codicons, others get emoji.
    • catalogInlayHints: boolean — VS Code keeps its custom decorations; non-VS Code clients get LSP-native inlay hints.
  • DEFAULT_CLIENT_FEATURES co-located with the ClientFeatures interface in language-service/types.ts.

Language service

  • getConfig resolves keys in order: exact (npmx.foo.bar) → scoped (foo.bar) → nested under npmx. Lets Zed users write flat config without the prefix.
  • Hover output now goes through a single icon registry instead of repeating (useCodicons, codicon, emoji) triples at every call site.
  • New inlayHintProvider on the catalog plugin shows the resolved spec next to catalog:foo references, gated on ClientFeatures.catalogInlayHints.

VS Code

  • Sends iconStyle: 'codicon' and catalogInlayHints: false in initializationOptions to preserve existing UX.
  • Removes the now-unused extensions/vscode/src/request.ts.

Tests

  • workspace.test.ts covers detectPackageManagerFromProject (supported PMs, unsupported, no detection).
  • hover.test.ts snapshots renderHoverMarkdown for both icon styles plus the JSR fallback path.

I'm not sure if it's too early to consider supporting editors beyond VS Code, but I've been using Zed and really enjoying it. I also love using npmx, so I thought this might be helpful.

I haven’t published the extension for Zed myself because I felt that might be stepping beyond my role and potentially disrespectful to the original maintainers, especially since expanding support to another editor is ultimately a project decision. I just wanted to share this in case it could be useful and help move things forward.

There are also quite a few choices in the code that reflect my personal preferences—such as using emoji or inlay hints—so please feel free to adjust or change anything as needed.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Apr 27, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​zed_extension_api@​0.7.08210093100100
Addedcargo/​serde_json@​1.0.1498210093100100

View full report

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

📝 Walkthrough

Walkthrough

Adds an in-repo Zed extension that launches the shared npmx language server over stdio and forwards lsp.npmx.settings into the npmx workspace configuration. Removes the shared GET_PACKAGE_MANAGER protocol and the VS Code request handler; VS Code client startup now passes initializationOptions (including catalogInlayHints and iconStyle) and no longer registers that custom request. Package-manager detection is moved to local detection via package-manager-detector with an exported detectPackageManagerFromProject. Workspace state now stores client features (get/set). Language-service changes add catalog inlay hints and icon-style-aware hover rendering; tests and Zed packaging/CI files are added/updated.

Possibly related PRs

  • npmx-dev/vscode-npmx#106: Modifies package-manager detection and workspace adapter code overlapping the exported detectPackageManagerFromProject and workspace-state changes.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description comprehensively outlines the changes: adding a Zed extension, making the language server editor-agnostic, removing VS Code-specific IPC, introducing ClientFeatures negotiation, refactoring config resolution, and updating tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
packages/language-server/src/server.ts (1)

1-4: Optional: collapse the two imports from npmx-language-service/types.

Lines 1 and 4 both import from the same module; the type and value imports can be combined into a single statement using inline type markers without violating the "type imports first" ordering rule.

♻️ Proposed refactor
-import type { ClientFeatures } from 'npmx-language-service/types'
 import { createConnection, createServer, createSimpleProject } from '@volar/language-server/node'
 import { createNpmxLanguageServicePlugins } from 'npmx-language-service'
-import { DEFAULT_CLIENT_FEATURES } from 'npmx-language-service/types'
+import { type ClientFeatures, DEFAULT_CLIENT_FEATURES } from 'npmx-language-service/types'
packages/language-server/src/workspace.ts (1)

17-32: Confirm the intent of stopDir: rootPath and the implicit 'deno' → 'npm' mapping.

By default detect() crawls upward to parent directories, but pinning stopDir to cwd here effectively restricts detection to the workspace root only. That is likely what you want for a per-folder WorkspaceContext, but it does mean that opening, e.g., a sub-folder of a monorepo will silently fall back to 'npm' rather than picking up the parent's lockfile. Worth confirming this matches your intended UX.

Separately, package-manager-detector can also return 'deno'. The default branch maps it (and any future agent) to 'npm'. If 'deno' is intentionally unsupported by PackageManager, consider an explicit case 'deno': to make the fallback intent obvious and avoid a silent regression should PackageManager ever gain a 'deno' member.

packages/language-service/src/plugins/hover.ts (1)

10-35: Clean icon registry; small consolidation opportunity.

The single ICONS registry plus iconLabel/iconText helpers nicely centralise the codicon-vs-emoji branching previously scattered through the renderer. The   vs regular space split between iconLabel (used inside […](…) link text where Markdown otherwise collapses whitespace) and iconText (plain text) is a sensible distinction.

If you'd like to trim a few lines, the two helpers differ only in the separator character and could be unified, e.g.:

♻️ Optional consolidation
-function iconLabel(iconStyle: IconStyle, name: IconName, label: string): string {
-  const { codicon, emoji } = ICONS[name]
-  return iconStyle === 'codicon'
-    ? `$(${codicon}) ${label}`
-    : `${emoji} ${label}`
-}
-
-function iconText(iconStyle: IconStyle, name: IconName, text: string): string {
-  const { codicon, emoji } = ICONS[name]
-  return iconStyle === 'codicon'
-    ? `$(${codicon}) ${text}`
-    : `${emoji} ${text}`
-}
+function renderIcon(iconStyle: IconStyle, name: IconName, text: string, sep: ' ' | ' ' = ' '): string {
+  const { codicon, emoji } = ICONS[name]
+  return iconStyle === 'codicon' ? `$(${codicon})${sep}${text}` : `${emoji} ${text}`
+}

Happy to leave as-is for clarity.

packages/language-service/src/config.ts (1)

90-115: Up to three sequential workspace/configuration round-trips per getConfig call.

getConfig is invoked on every hover/completion/diagnostic; in the worst case it now performs three sequential getConfiguration(...) LSP requests (section, spec.scopedKey, scopedConfigs.scope). VS Code clients typically resolve configuration synchronously from a local cache, but for Zed (and any client without aggressive caching) every miss is a real round-trip on the request thread, multiplied across feature calls.

Two low-effort mitigations worth considering:

  • Short-circuit when a known shape is expected (e.g., cache the detected "shape" per workspace after the first successful resolution and only query that source thereafter).
  • Fetch the root npmx object once per request and serve all keys from it, falling back to per-key calls only if the root is undefined.

Not a blocker — flagging because it's invisible from the diff but compounds quickly under load.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5d86122a-beae-4cda-b56c-61f68b3e21b1

📥 Commits

Reviewing files that changed from the base of the PR and between 8cce1e8 and fc8da4e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (20)
  • README.md
  • extensions/vscode/src/client.ts
  • extensions/vscode/src/request.ts
  • extensions/zed/.gitignore
  • extensions/zed/Cargo.toml
  • extensions/zed/README.md
  • extensions/zed/extension.toml
  • extensions/zed/src/lib.rs
  • packages/language-server/package.json
  • packages/language-server/src/server.ts
  • packages/language-server/src/workspace.test.ts
  • packages/language-server/src/workspace.ts
  • packages/language-server/tsdown.config.ts
  • packages/language-service/src/config.ts
  • packages/language-service/src/plugins/catalog.ts
  • packages/language-service/src/plugins/hover.test.ts
  • packages/language-service/src/plugins/hover.ts
  • packages/language-service/src/types.ts
  • packages/shared/src/protocol.ts
  • pnpm-workspace.yaml
💤 Files with no reviewable changes (1)
  • extensions/vscode/src/request.ts

Comment thread extensions/zed/Cargo.toml
Comment thread extensions/zed/src/lib.rs
Comment thread packages/language-server/src/workspace.ts Outdated
Comment thread packages/language-service/src/plugins/catalog.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
.github/workflows/publish-language-server.yml (1)

26-26: ⚡ Quick win

Use JSON parsing instead of sed for version extraction.

Line 26 depends on exact whitespace/comma formatting in package.json, making the release check fragile. Prefer node -p "require('./packages/language-server/package.json').version" (or jq) for a stable check.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 05fa6e58-217f-470e-aeb9-9612c7aee164

📥 Commits

Reviewing files that changed from the base of the PR and between 8e6b4eb and 4e5451d.

📒 Files selected for processing (8)
  • .github/workflows/publish-language-server.yml
  • .github/workflows/publish-vscode.yml
  • .github/workflows/publish-zed.yml
  • extensions/zed/Cargo.toml
  • extensions/zed/LICENSE.md
  • extensions/zed/extension.toml
  • package.json
  • scripts/sync-zed-version.mjs
✅ Files skipped from review due to trivial changes (3)
  • extensions/zed/Cargo.toml
  • scripts/sync-zed-version.mjs
  • extensions/zed/extension.toml

Comment on lines +4 to +8
# TODO: Restore this trigger after these Zed release changes are merged to the main branch.
# push:
# tags:
# - v*
workflow_dispatch:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not merge with tag publishing disabled unless this is intentionally permanent.

Line 4–Line 8 remove automatic tag-triggered publishing, which can silently halt VS Code release automation on v* tags. If this is temporary, gate it behind a short-lived branch-only change or restore the trigger before merge.

fi

- name: Update Zed extensions registry
uses: huacnlee/zed-extension-action@v2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read the relevant section
if [ -f .github/workflows/publish-zed.yml ]; then
  echo "=== File exists, reading lines 20-40 for context ==="
  sed -n '20,40p' .github/workflows/publish-zed.yml | cat -n
  echo ""
  echo "=== Full workflow file ==="
  cat -n .github/workflows/publish-zed.yml
else
  echo "File not found"
fi

Repository: npmx-dev/vscode-npmx

Length of output: 2566


🏁 Script executed:

# Check other workflow files to understand pinning patterns in the repo
echo "=== Searching for action usage patterns across all workflows ==="
find .github/workflows -name "*.yml" -o -name "*.yaml" | while read f; do
  echo "--- File: $f ---"
  rg "uses:" "$f" | head -20
done

Repository: npmx-dev/vscode-npmx

Length of output: 1127


Pin the Zed publish action to a commit SHA.

Line 30 uses a mutable tag (@v2) whilst all other actions in the repository (across six workflows) are pinned to commit SHAs. Update to a specific commit digest to maintain consistency with repository security practices and reduce supply-chain risk in release workflows.

Comment on lines +33 to +37
# TODO: Update this to the correct fork before enabling automated publishing.
push-to: withxat/issue-zed-extensions
env:
# TODO: Add the COMMITTER_TOKEN secret before enabling automated publishing.
COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not keep placeholder publishing config in an active tag-triggered workflow.

Line 33–Line 37 still point to a temporary fork and TODO token setup while Line 4–Line 6 already publish on v* tags. This can publish to the wrong destination or fail releases; disable the trigger until final push-to and secrets are ready.

@withxat
Copy link
Copy Markdown
Author

withxat commented May 12, 2026

@9romise Quick update + a question about next steps:

The implementation is mostly done, and I’ve been testing it locally for a while — it seems to work pretty well.

What remains is mainly packaging / publishing. Since this is Rust-based, it doesn’t fit perfectly into the existing VS Code extension workflow.

One blocker is the LSP binary: it doesn’t seem to be published anywhere right now, while Zed expects a downloadable artifact. My current workaround is to fetch it from GitHub Release assets, though publishing the LSP officially might be a cleaner option.

Would you prefer this to live in this repo and go through the normal release flow, or should it be maintained as a separate repo/package?

Happy to help with either direction.

@9romise
Copy link
Copy Markdown
Member

9romise commented May 14, 2026

Would you prefer this to live in this repo and go through the normal release flow, or should it be maintained as a separate repo/package?

Happy to help with either direction.

Sorry for the late reply!
I'd prefer to keep it in this repo and improve our release workflow.
Thank you so much for your great work on this! I'm not familiar with Rust and Zed, but if there's anything I can help with, feel free to ping me.

@withxat
Copy link
Copy Markdown
Author

withxat commented May 22, 2026

@9romise Sorry I’ve been busy with my own stuff lately. This PR is pretty much complete now, so could you please review whether the changes to the LSP and VS Code extension look appropriate?

The main change is adding a parameter to determine whether to use VSCodium-specific icons or plain emoji, with the goal of making the LSP more editor-agnostic.

I also added and updated the workflows, and included the unified version bump changes as well.

As for the Zed extension, like I mentioned earlier, the biggest blocker is that the LSP needs to be published separately—either as a package on npmjs, or via the approach I’ve implemented here: distributing it through GitHub Releases.

PTAL :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants