Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e6b8b2d
refactor(language-core): add package manager fallback detection
withxat Apr 24, 2026
c9e210b
refactor(language-service): support nested config fallbacks
withxat Apr 24, 2026
41d29a8
feat(zed): add dev extension launcher
withxat Apr 24, 2026
8baf11f
refactor(language-service): preserve editor-specific ux
withxat Apr 24, 2026
e922aaa
refactor(workspace): adopt package-manager-detector fallback
withxat Apr 24, 2026
a4ef65d
refactor(language-server): unify package manager detection
withxat Apr 27, 2026
3601a47
chore(language-server): align detector dependency placement
withxat Apr 27, 2026
bd342c7
refactor(language-server): use client feature flags
withxat Apr 27, 2026
5723821
fix(language-service): use unicode hover icons outside vscode
withxat Apr 27, 2026
fea3872
refactor(language-service): rename markdown icons option
withxat Apr 27, 2026
2fe4301
style(language-service): use emoji hover icons
withxat Apr 27, 2026
e94126d
refactor(language-service): simplify config lookup
withxat Apr 27, 2026
07e5986
docs: update zed extension readmes
withxat Apr 27, 2026
fc8da4e
feat: polish the implementation
withxat Apr 27, 2026
eaae9c3
chore(zed): mark extension crate private
withxat May 7, 2026
a4311c0
feat(zed): pass client feature initialization options
withxat May 7, 2026
91eebca
fix(language-server): use detector subpath import
withxat May 7, 2026
8e6b4eb
fix(language-service): remove duplicate inlay hint spacing
withxat May 7, 2026
b53f855
feat: sync version with vscode extension
withxat May 8, 2026
c751f50
ci(workflows): add zed release workflows
withxat May 8, 2026
aaca028
ci(release): sync zed versions during bump
withxat May 8, 2026
9fe8e0c
chore(zed): align version and add license
withxat May 8, 2026
5ae5306
ci(workflows): simplify release triggers
withxat May 8, 2026
4e5451d
docs(workflows): clarify zed publish TODOs
withxat May 8, 2026
055fd84
ci(test): reuse built lsp packages
withxat Jun 15, 2026
df9afd9
chore: merge upstream main
withxat Jun 15, 2026
3b31530
ci(release): restore vscode tag trigger
withxat Jun 15, 2026
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
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
| Package | Description |
| ------- | ----------- |
| [`extensions/vscode`](./extensions/vscode) | [VS Code extension](https://marketplace.visualstudio.com/items?itemName=npmx-dev.vscode-npmx) for npmx |
| [`extensions/zed`](./extensions/zed) | Zed extension for npmx, backed by the shared language server |
| [`packages/shared`](./packages/shared) | Shared constants, types, and LSP protocol definitions |
| [`packages/language-core`](./packages/language-core) | Core logic: extractors, API clients, workspace context |
| [`packages/language-service`](./packages/language-service) | Volar language service plugins (hover, completion, diagnostics, etc.) |
| [`packages/language-server`](./packages/language-server) | Volar language server |

## Features

- **Hover Information** – Quick links to package details and documentation on [npmx.dev](https://npmx.dev), with provenance verification status.
- **Hover Information** – Quick links to package details and documentation on [npmx.dev](https://npmx.dev), with provenance verification status. VS Code uses codicons, while other editors use emoji icons.
- **Version Completion** – Autocomplete package versions with provenance filtering and prerelease exclusion support.
- **Workspace-Aware Resolution** – Dependencies in `package.json`, `pnpm-workspace.yaml`, and `.yarnrc.yml` are resolved from a shared workspace context, including npm, pnpm, yarn, and bun package managers plus root `package.json` catalogs and workspace references.
- **Diagnostics**
Expand All @@ -36,6 +37,11 @@
- Open [npmx.dev](https://npmx.dev) in external browser
- Open `node_modules` files on [npmx.dev](https://npmx.dev) code viewer with syntax highlighting (from editor title, editor context menu, explorer context menu, or command palette)

## Editor Support

- **VS Code** – Primary extension package with hover, completion, diagnostics, document links, catalog decorations, code actions, and commands.
- **Zed** – In-repo development extension using the same language server over stdio. It supports the shared LSP features and forwards `lsp.npmx.settings` as `npmx` workspace configuration.

## Related

- [npmx.dev](https://npmx.dev) – A fast, modern browser for the npm registry
Expand Down
11 changes: 8 additions & 3 deletions extensions/vscode/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { LanguageClient, TransportKind } from '@volar/vscode/node'
import { DEPENDENCY_FILE_GLOB } from 'npmx-language-core/constants'
import { displayName, extensionId } from 'npmx-shared/meta'
import { Hover, MarkdownString } from 'vscode'
import { registerRequests } from './request'

const SUPPORTED_LANGUAGES = [
'javascript',
Expand Down Expand Up @@ -68,12 +67,18 @@ export function launch(serverPath: string) {
synchronize: {
configurationSection: [displayName],
},
initializationOptions: {
npmx: {
clientFeatures: {
catalogInlayHints: false,
iconStyle: 'codicon',
},
},
},
diagnosticCollectionName: displayName,
outputChannelName: `${displayName} Language Server`,
},
)

registerRequests(client)

return { client, ready: client.start() }
}
25 changes: 0 additions & 25 deletions extensions/vscode/src/request.ts

This file was deleted.

3 changes: 3 additions & 0 deletions extensions/zed/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
/extension.wasm
/Cargo.lock
12 changes: 12 additions & 0 deletions extensions/zed/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "zed-npmx"
version = "0.0.1"
edition = "2021"
license = "MIT"

[lib]
crate-type = [ "cdylib" ]

[dependencies]
serde_json = "1"
zed_extension_api = "0.7.0"
Comment thread
withxat marked this conversation as resolved.
84 changes: 84 additions & 0 deletions extensions/zed/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# npmx for Zed

This is the in-repo Zed extension for `npmx`. It runs the shared `npmx-language-server`
over stdio, so Zed gets the same core package intelligence used by the VS Code extension.

## Status

- Uses the shared `npmx-language-server`
- Targets local development from this monorepo first
- Defaults to `packages/language-server/dist/index.cjs`
- Launches the language server over `--stdio`
- Supports overriding the launched command through Zed `lsp.npmx.binary` settings
- Forwards `lsp.npmx.settings` to the language server as `npmx` workspace configuration

## Features

- Hover links to package pages and docs on [npmx.dev](https://npmx.dev)
- Emoji hover icons for non-VS Code editors
- Version completion with provenance and prerelease settings
- Diagnostics for upgrades, deprecations, replacements, vulnerabilities, dist tags, and engine mismatches
- Document links for package names
- Workspace-aware dependency resolution for npm, pnpm, yarn, and bun projects

## Local Development

1. Build the language server from the repo root with `pnpm build`.
2. In Zed, install `extensions/zed` as a dev extension.
3. If you want a custom launch command, configure `lsp.npmx.binary` in your Zed settings.

## Settings

Zed settings under `lsp.npmx.settings` are forwarded directly to the language server.
Use scoped npmx settings without the leading `npmx.` prefix:

```json
{
"lsp": {
"npmx": {
"settings": {
"hover": {
"enabled": true
},
"completion": {
"version": "provenance-only",
"excludePrerelease": true
},
"diagnostics": {
"upgrade": true,
"deprecation": true,
"replacement": true,
"vulnerability": true,
"distTag": true,
"engineMismatch": true
},
"packageLinks": "declared"
}
}
}
}
```

To override the launched language server command:

```json
{
"lsp": {
"npmx": {
"binary": {
"path": "node",
"arguments": [
"/absolute/path/to/vscode-npmx/packages/language-server/dist/index.cjs",
"--stdio"
]
}
}
}
}
```

## Notes

- Zed dev extensions require Rust installed via `rustup`; the Zed docs explicitly call out that Homebrew Rust will not work for dev extension compilation.
- This dev extension expects the repo-local language server bundle at `packages/language-server/dist/index.cjs`, so build the monorepo before installing it in Zed.
- If you override `lsp.npmx.binary`, make sure the launched server process still receives an LSP transport argument such as `--stdio`.
34 changes: 34 additions & 0 deletions extensions/zed/extension.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
id = "npmx"
name = "npmx"
description = "npmx language support for Zed"
version = "0.0.1"
schema_version = 1
authors = [ "Xat <i@xat.sh>" ]
repository = "https://github.com/npmx-dev/vscode-npmx/tree/main/extensions/zed"

[language_servers.npmx]
name = "npmx"
languages = [
"JSON",
"YAML",
"JavaScript",
"JSX",
"TypeScript",
"TSX",
"HTML",
"Vue",
"Astro",
"Svelte"
]

[language_servers.npmx.language_ids]
JSON = "json"
YAML = "yaml"
JavaScript = "javascript"
JSX = "javascriptreact"
TypeScript = "typescript"
TSX = "typescriptreact"
HTML = "html"
Vue = "vue"
Astro = "astro"
Svelte = "svelte"
70 changes: 70 additions & 0 deletions extensions/zed/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use zed_extension_api::{self as zed, serde_json, settings::LspSettings, LanguageServerId};

struct NpmxExtension;

impl NpmxExtension {
fn language_server_settings(
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> LspSettings {
LspSettings::for_worktree(language_server_id.as_ref(), worktree)
.ok()
.unwrap_or_default()
}

fn default_server_script() -> String {
format!(
"{}/../../packages/language-server/dist/index.cjs",
env!("CARGO_MANIFEST_DIR")
)
}
}

impl zed::Extension for NpmxExtension {
fn new() -> Self {
Self
}

fn language_server_command(
&mut self,
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> zed::Result<zed::Command> {
let lsp_settings = Self::language_server_settings(language_server_id, worktree);
if let Some(binary) = lsp_settings.binary {
let command = match binary.path {
Some(path) => path,
None => zed::node_binary_path()?,
};
let args = binary.arguments.unwrap_or_default();
let env = worktree
.shell_env()
.into_iter()
.chain(binary.env.unwrap_or_default())
.collect();

return Ok(zed::Command { command, args, env });
}

Ok(zed::Command {
command: zed::node_binary_path()?,
args: vec![Self::default_server_script(), String::from("--stdio")],
env: worktree.shell_env().into_iter().collect(),
})
}

fn language_server_workspace_configuration(
&mut self,
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> zed::Result<Option<serde_json::Value>> {
let settings = Self::language_server_settings(language_server_id, worktree);
let workspace_settings = settings.settings.unwrap_or_default();

Ok(Some(serde_json::json!({
"npmx": workspace_settings
})))
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

zed::register_extension!(NpmxExtension);
2 changes: 2 additions & 0 deletions packages/language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"npmx-language-service": "workspace:*",
"npmx-shared": "workspace:*",
"ocache": "catalog:inline",
"package-manager-detector": "catalog:inline",
"vscode-uri": "catalog:lsp"
},
"inlinedDependencies": {
Expand All @@ -42,6 +43,7 @@
"ocache": "0.1.4",
"ofetch": "2.0.0-alpha.3",
"ohash": "2.0.11",
"package-manager-detector": "1.6.0",
"path-browserify": "1.0.1",
"request-light": "0.7.0",
"semver": "7.7.4",
Expand Down
47 changes: 36 additions & 11 deletions packages/language-server/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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 { name, version } from '../package.json' with { type: 'json' }
import { registerRequests } from './request'
import { createWorkspaceState } from './workspace'
Expand All @@ -12,17 +14,21 @@ export function startServer() {

connection.listen()

connection.onInitialize((params) => ({
serverInfo: {
name,
version,
},
...server.initialize(
params,
createSimpleProject([]),
createNpmxLanguageServicePlugins(workspaceState),
),
}))
connection.onInitialize((params) => {
workspaceState.setClientFeatures(readClientFeatures(params.initializationOptions))

return {
serverInfo: {
name,
version,
},
...server.initialize(
params,
createSimpleProject([]),
createNpmxLanguageServicePlugins(workspaceState),
),
}
})
connection.onInitialized(() => {
connection.console.info('npmx language server initialized')

Expand All @@ -32,3 +38,22 @@ export function startServer() {

registerRequests(connection, workspaceState)
}

function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}

function readClientFeatures(value: unknown): ClientFeatures {
if (!isObject(value) || !isObject(value.npmx) || !isObject(value.npmx.clientFeatures))
return DEFAULT_CLIENT_FEATURES

const cf = value.npmx.clientFeatures
return {
catalogInlayHints: typeof cf.catalogInlayHints === 'boolean'
? cf.catalogInlayHints
: DEFAULT_CLIENT_FEATURES.catalogInlayHints,
iconStyle: cf.iconStyle === 'codicon' || cf.iconStyle === 'emoji'
? cf.iconStyle
: DEFAULT_CLIENT_FEATURES.iconStyle,
}
}
Loading