Skip to content

Latest commit

 

History

History
1070 lines (867 loc) · 27.9 KB

File metadata and controls

1070 lines (867 loc) · 27.9 KB
name zed-editor
description Zed Editor extensions - Rust/Wasm plugins, LSP servers, Tree-sitter grammars, themes, MCP servers, slash commands, debug adapters
metadata
author version tags
mte90
1.0.0
zed
editor
extension
rust
wasm
tree-sitter
lsp
plugin

Zed Editor Extensions

Build extensions for the Zed editor — high-performance, multiplayer code editor built in Rust.

Extensions are Rust crates compiled to WebAssembly that run in a sandboxed Wasmtime environment. They can provide languages, themes, debuggers, snippets, MCP servers, and AI slash commands.

Overview

What extensions can provide:

  • Language support (Tree-sitter grammars + LSP servers)
  • Color themes and icon themes
  • Snippet collections
  • Debug adapters (DAP)
  • MCP context servers (for AI assistant)
  • Slash commands (for AI assistant)
  • Agent servers (ACP)

What extensions CANNOT do:

  • Create custom UI panels or windows (no GPUI access)
  • Modify Zed's core UI behavior
  • Arbitrary filesystem access (restricted by capabilities)
  • Run system commands without user-granted capability

Directory Structure

my-extension/
├── extension.toml          # Required manifest
├── Cargo.toml              # Required for Rust extensions (cdylib)
├── src/
│   └── lib.rs              # Extension implementation
├── languages/
│   └── my-language/
│       ├── config.toml     # Language metadata
│       ├── highlights.scm  # Syntax highlighting
│       ├── brackets.scm    # Bracket matching
│       ├── outline.scm     # Code outline
│       ├── indents.scm     # Auto-indentation
│       ├── injections.scm  # Language injections
│       ├── overrides.scm   # Editor behavior overrides
│       ├── textobjects.scm # Text objects (Vim)
│       ├── redactions.scm  # Screen share redaction
│       ├── runnables.scm   # Runnable code detection
│       └── semantic_token_rules.json  # LSP semantic tokens
├── themes/
│   └── my-theme.json       # Theme definitions
├── icon-themes/
│   └── my-icons.json       # Icon theme definitions
└── snippets/
    └── snippets.json        # Snippet definitions

Extension Manifest (extension.toml)

id = "my-extension"
name = "My Extension"
version = "0.1.0"
schema_version = 1
authors = ["Your Name <you@example.com>"]
description = "Provides support for My Language"
repository = "https://github.com/you/my-zed-extension"

# Tree-sitter grammars
[grammars.my-language]
repository = "https://github.com/tree-sitter/tree-sitter-my-language"
rev = "abc123def456"

# For local development, use file:// URL
# [grammars.my-language]
# repository = "file:///path/to/tree-sitter-my-language"

# Language servers
[language_servers.my-lsp]
name = "My Language Server"
languages = ["My Language"]

# Multi-language server with LSP ID mapping
# [language_servers.my-lsp]
# name = "Whatever LSP"
# languages = ["JavaScript", "HTML", "CSS"]
#
# [language_servers.my-lsp.language_ids]
# "JavaScript" = "javascript"
# "TSX" = "typescriptreact"
# "HTML" = "html"

# Debug adapters
[debug_adapters.my-dap]
schema_path = "debug_adapter_schemas/my-dap.json"

# MCP context servers
[context_servers.my-mcp]

# Agent servers
[agent_servers.my-agent]
name = "My AI Agent"
icon = "icon/agent.svg"

[agent_servers.my-agent.env]
AGENT_LOG_LEVEL = "info"

[agent_servers.my-agent.targets.darwin-aarch64]
archive = "https://github.com/owner/repo/releases/download/v1.0.0/agent-darwin-arm64.tar.gz"
cmd = "./agent"
args = ["--serve"]
sha256 = "abc123..."

[agent_servers.my-agent.targets.linux-x86_64]
archive = "https://github.com/owner/repo/releases/download/v1.0.0/agent-linux-x64.tar.gz"
cmd = "./agent"
args = ["--serve"]

Rust Extension Setup

Cargo.toml

[package]
name = "my-extension"
version = "0.1.0"
edition = "2021"

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

[dependencies]
zed_extension_api = "0.1.0"  # Use latest from crates.io
serde = "1.0"
serde_json = "1.0"

Important: Use the latest zed_extension_api version from crates.io. Check compatible Zed versions.

src/lib.rs — Basic Extension

use zed_extension_api as zed;

struct MyExtension;

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

zed::register_extension!(MyExtension);

Language Server (LSP) Integration

Configuration in extension.toml

[language_servers.my-language-server]
name = "My Language LSP"
languages = ["My Language"]

Implementation

use zed_extension_api as zed;

struct MyExtension {
    cached_binary_path: Option<String>,
}

impl zed::Extension for MyExtension {
    fn new() -> Self {
        Self { cached_binary_path: None }
    }

    fn language_server_command(
        &mut self,
        _language_server_id: &zed::LanguageServerId,
        worktree: &zed::Worktree,
    ) -> zed::Result<zed::Command> {
        // Option 1: Use binary from PATH
        let path = worktree
            .which("my-language-server")
            .ok_or_else(|| "my-language-server not found in PATH".to_string())?;

        Ok(zed::Command {
            command: path,
            args: vec!["--stdio".to_string()],
            env: worktree.shell_env(),
        })
    }
}

Downloading Language Server from GitHub

fn language_server_command(
    &mut self,
    _language_server_id: &zed::LanguageServerId,
    worktree: &zed::Worktree,
) -> zed::Result<zed::Command> {
    let binary_path = format!(
        "{}/my-language-server-{}",
        worktree.root_path(),
        std::env::consts::OS
    );

    if !std::path::Path::new(&binary_path).exists() {
        let release = zed::latest_github_release(
            "owner/my-language-server",
            zed::GithubReleaseOptions { require_assets: true },
        )?;

        let (os, arch) = zed::current_platform();
        let asset_name = format!("server-{}-{}.tar.gz", os, arch);

        let asset = release.assets
            .iter()
            .find(|a| a.name.contains(&asset_name))
            .ok_or_else(|| format!("No release asset matching '{}'", asset_name))?;

        zed::download_file(&asset.browser_download_url, &binary_path)?;
        zed::make_file_executable(&binary_path)?;
    }

    Ok(zed::Command {
        command: binary_path,
        args: vec!["--stdio".to_string()],
        env: worktree.shell_env(),
    })
}

Using npm Packages

fn language_server_command(
    &mut self,
    _language_server_id: &zed::LanguageServerId,
    worktree: &zed::Worktree,
) -> zed::Result<zed::Command> {
    if zed::npm_package_installed_version("my-language-server").is_none() {
        zed::npm_install_package("my-language-server")?;
    }

    Ok(zed::Command {
        command: format!("{}/node_modules/.bin/my-language-server",
            zed::node_binary_path()),
        args: vec!["--stdio".to_string()],
        env: worktree.shell_env(),
    })
}

LSP Initialization Options

fn language_server_initialization_options(
    &mut self,
    _language_server_id: &zed::LanguageServerId,
    _worktree: &zed::Worktree,
) -> zed::Result<Option<serde_json::Value>> {
    Ok(Some(serde_json::json!({
        "settings": {
            "enableFormatting": true,
            "lint": { "enable": true }
        }
    })))
}

Custom Completion Labels

fn label_for_completion(
    &self,
    _language_server_id: &zed::LanguageServerId,
    completion: zed::lsp::Completion,
) -> Option<zed::CodeLabel> {
    Some(zed::CodeLabel {
        text: completion.label.clone(),
        filter_range: 0..completion.label.len(),
        display_range: 0..completion.label.len(),
        syntax_highlights: vec![],
    })
}

Language Support (Tree-sitter)

Language config.toml

Place in languages/my-language/config.toml:

name = "My Language"
grammar = "my-language"               # Must match grammar name in extension.toml
path_suffixes = ["myl", "mylang"]     # File extensions
line_comments = ["// ", "# "]          # Line comment prefixes
block_comments = [{ start = "/*", end = "*/" }]
tab_size = 4
hard_tabs = false
first_line_pattern = "^#!.*myl"       # Shebang detection
word_characters = ["#", "$", "-"]     # Non-alpha chars that are part of words

# Bracket auto-closing configuration
brackets = [
    { start = "{", end = "}", close = true, newline = true },
    { start = "(", end = ")", close = true, newline = true },
    { start = "[", end = "]", close = true, newline = true },
    { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
]

# Scope-specific overrides
[overrides.string]
completion_query_characters = ["-", "."]

Tree-sitter Query Files

All .scm files go in languages/my-language/.

highlights.scm — Syntax Highlighting

(string) @string
(comment) @comment
(number) @number
(keyword) @keyword
(function name: (identifier) @function)
(type_identifier) @type
(identifier) @variable
(property_identifier) @property
(operator) @operator
(constant) @constant
(boolean) @boolean

Supported captures:

Capture Description
@string String literals
@string.escape Escaped characters
@string.regex Regular expressions
@string.special Special strings
@comment Comments
@comment.doc Doc comments
@keyword Keywords
@number Numeric values
@boolean Boolean values
@function Functions
@type Types
@type.builtin Built-in types
@variable Variables
@variable.special Special variables
@variable.parameter Parameters
@property Properties
@operator Operators
@constant Constants
@constant.builtin Built-in constants
@constructor Constructors
@attribute Attributes
@tag Tags
@label Labels
@punctuation Punctuation
@punctuation.bracket Brackets
@punctuation.delimiter Delimiters
@preproc Preprocessor directives
@embedded Embedded content
@enum Enumerations
@variant Variants

Fallback captures: Multiple captures on same node define fallback highlights:

(type_identifier) @type @variable

Zed resolves right-to-left: tries @variable first, falls back to @type.

brackets.scm — Bracket Matching

("{" @open "}" @close)
("[" @open "]" @close)
("(" @open ")" @close)
("\"" @open "\"" @close) (#set! rainbow.exclude)  ; Exclude from rainbow brackets

outline.scm — Code Outline

(function_definition name: (identifier) @name) @item
(class_definition name: (identifier) @name) @item
(method_definition name: (identifier) @name) @item

Captures: @name (item name), @item (entire item), @context (context info), @annotation (decorators, doc comments).

indents.scm — Auto-Indentation

(array "]" @end) @indent
(object "}" @end) @indent
(function_definition body: (block "{" @indent))

injections.scm — Language Injections

(fenced_code_block
    (info_string (language) @injection.language)
    (code_fence_content) @injection.content)

((string_content) @injection.content
    (#set! injection.language "sql"))

textobjects.scm — Vim Text Objects

(method_definition
    body: (_
        "{"
        (_)* @function.inside
        "}")) @function.around

(class_definition
    body: (_
        "{"
        (_)* @class.inside
        "}")) @class.around

(comment)+ @comment.around

Captures: @function.around, @function.inside, @class.around, @class.inside, @comment.around, @comment.inside.

redactions.scm — Screen Share Privacy

(pair value: (string) @redact)
(pair value: (number) @redact)
(password_field) @redact

runnables.scm — Runnable Code Detection

(
    (document
        (object
            (pair
                key: (string (string_content) @_name
                    (#eq? @_name "scripts"))
                value: (object
                    (pair
                        key: (string (string_content) @run))
                    )
                )
            )
        )
    )
)

Extra captures (except _ prefixed) become ZED_CUSTOM_<capture_name> env vars.


MCP Server Extensions

Registration

[context_servers.my-mcp]

Implementation

fn context_server_command(
    &mut self,
    _context_server_id: &zed::ContextServerId,
    _project: &zed::Project,
) -> zed::Result<zed::Command> {
    Ok(zed::Command {
        command: "my-mcp-server".to_string(),
        args: vec!["--stdio".to_string()],
        env: std::env::vars().collect(),
    })
}

Slash Commands (AI Assistant)

Registration in extension.toml

[[slash_commands.my-command]]
description = "Does something useful"
requires_argument = true

Implementation

fn run_slash_command(
    &self,
    command: zed::SlashCommand,
    args: Vec<String>,
    worktree: Option<&zed::Worktree>,
) -> zed::Result<zed::SlashCommandOutput, String> {
    match command.name.as_str() {
        "my-command" => {
            let result = do_something(&args)?;
            Ok(zed::SlashCommandOutput {
                text: result,
                sections: vec![],
                attachments: vec![],
            })
        }
        _ => Err(format!("Unknown command: {}", command.name)),
    }
}

fn complete_slash_command_argument(
    &self,
    command: zed::SlashCommand,
    _args: Vec<String>,
) -> zed::Result<Vec<zed::SlashCommandArgumentCompletion>> {
    Ok(vec![
        zed::SlashCommandArgumentCompletion {
            label: "option-1".to_string(),
            new_text: "option-1".to_string(),
            run_command_in_query: false,
        },
    ])
}

Debugger Extensions (DAP)

Registration

[debug_adapters.my-dap]
schema_path = "debug_adapter_schemas/my-dap.json"

[debug_locators.my-locator]

Implementation

fn get_dap_binary(
    &mut self,
    adapter_name: String,
    _config: zed::DebugTaskDefinition,
    _user_provided_debug_adapter_path: Option<String>,
    worktree: &zed::Worktree,
) -> zed::Result<zed::DebugAdapterBinary, String> {
    let path = worktree
        .which(&adapter_name)
        .ok_or_else(|| format!("{} not found", adapter_name))?;

    Ok(zed::DebugAdapterBinary {
        command: Some(path),
        args: vec![],
        env: worktree.shell_env(),
        connection: None,
        use_tcp: false,
    })
}

Theme Extensions

Theme JSON Structure

Place in themes/my-theme.json. Follow schema: https://zed.dev/schema/themes/v0.2.0.json

{
    "name": "My Theme Family",
    "author": "Your Name",
    "themes": [
        {
            "name": "My Dark Theme",
            "appearance": "dark",
            "style": {
                "background": "#1a1b26",
                "foreground": "#a9b1d6",
                "accent": "#7aa2f7",
                "border": "#292e42",
                "border.variant": "#1f2335",
                "surface.background": "#1a1b26",
                "title_bar.background": "#1a1b26",
                "toolbar.background": "#1a1b26",
                "editor.background": "#1a1b26",
                "editor.foreground": "#a9b1d6",
                "editor.gutter.background": "#1a1b26",
                "editor.active_line.background": "#292e42",
                "editor.line_number": "#3b4261",
                "editor.active_line_number": "#a9b1d6",
                "terminal.background": "#1a1b26",
                "terminal.foreground": "#a9b1d6",
                "terminal.ansi.black": "#24283b",
                "terminal.ansi.red": "#f7768e",
                "terminal.ansi.green": "#9ece6a",
                "terminal.ansi.yellow": "#e0af68",
                "terminal.ansi.blue": "#7aa2f7",
                "terminal.ansi.magenta": "#ad8ee6",
                "terminal.ansi.cyan": "#7dcfff",
                "terminal.ansi.white": "#a9b1d6",
                "syntax": {
                    "keyword": { "color": "#bb9af7" },
                    "keyword.control": { "color": "#bb9af7" },
                    "string": { "color": "#9ece6a" },
                    "function": { "color": "#7aa2f7" },
                    "type": { "color": "#7dcfff" },
                    "comment": { "color": "#565f89", "font_style": "italic" },
                    "variable": { "color": "#a9b1d6" },
                    "number": { "color": "#ff9e64" },
                    "operator": { "color": "#89ddff" },
                    "property": { "color": "#73daca" },
                    "constant": { "color": "#ff9e64" },
                    "tag": { "color": "#f7768e" }
                },
                "players": [
                    { "cursor": "#7aa2f7", "selection": "#283457" }
                ]
            }
        },
        {
            "name": "My Light Theme",
            "appearance": "light",
            "style": {
                "background": "#e1e2e7",
                "foreground": "#3760bf",
                "editor.background": "#e1e2e7",
                "editor.foreground": "#3760bf"
            }
        }
    ]
}

Use the Theme Builder to visually design themes.


Snippet Extensions

Snippet JSON Format

Place in snippets/my-language.json:

{
    "function": {
        "prefix": "fn",
        "body": [
            "fn ${1:function_name}(${2:args}) -> ${3:ReturnType} {",
            "    ${4:// TODO: implement}",
            "}",
            "$0"
        ],
        "description": "Create a new function"
    },
    "test": {
        "prefix": "test",
        "body": [
            "#[test]",
            "fn ${1:test_name}() {",
            "    ${2:// TODO: write test}",
            "}"
        ],
        "description": "Create a test function"
    },
    "struct": {
        "prefix": "struct",
        "body": [
            "struct ${1:Name} {",
            "    ${2:field}: ${3:Type},",
            "}"
        ],
        "description": "Create a struct"
    }
}

Tabstop syntax: $0 (final cursor), ${1:placeholder}, ${2:default_value}.


Extension Capabilities (Security)

Extensions run sandboxed. Users grant capabilities in settings:

{
    "granted_extension_capabilities": [
        { "kind": "process:exec", "command": "*", "args": ["**"] },
        { "kind": "download_file", "host": "github.com", "path": ["**"] },
        { "kind": "npm:install", "package": "*" }
    ]
}

Restricting Capabilities

{
    "granted_extension_capabilities": [
        { "kind": "process:exec", "command": "cargo", "args": ["**"] },
        { "kind": "download_file", "host": "github.com", "path": ["owner", "repo", "**"] }
    ]
}

Capability Kinds

Capability Controls
process:exec Execute external commands
download_file Download files from URLs
npm:install Install npm packages

To disable all capabilities: "granted_extension_capabilities": []


Development Workflow

Prerequisites

  • Rust installed via rustup (not Homebrew or system packages)
  • Node.js (for some extensions using npm packages)

Install Dev Extension

  1. Open Zed → Extensions page → Install Dev Extension
  2. Select your extension directory
  3. Dev extension overrides any published version

Debugging

# Run Zed in foreground for verbose logs
zed --foreground

# View logs in Zed: open command palette → "zed: open log"
# Log file locations:
#   Linux: ~/.config/zed/zed.log
#   macOS: ~/Library/Application Support/zed/zed.log

# stdout/stderr from extensions is forwarded to Zed process
# Use println!/dbg! in Rust code, visible with --foreground

Test Workflow

# 1. Make changes to extension code
# 2. In Zed: Extensions → click "Install Dev Extension" again to rebuild
# 3. Check Zed.log for errors
# 4. Test language features, LSP, themes, etc.

Publishing to Zed Marketplace

License Requirements

Extension repositories must include a license file at the root. Accepted:

  • Apache 2.0, MIT, BSD 2-Clause, BSD 3-Clause
  • CC BY 4.0, GPLv3, LGPLv3, Unlicense, zlib

Naming Rules

  • Extension IDs must be unique
  • Cannot contain "zed", "Zed", or "extension"
  • Theme extensions: suffix with -theme
  • Snippet extensions: suffix with -snippets
  • Language extensions: use the language name (e.g., rust, python)

Publishing Process

  1. Fork zed-industries/extensions

    Fork to a personal account (not org) so Zed staff can push changes to your PR.

  2. Add submodule:

git submodule add https://github.com/you/my-zed-extension.git extensions/my-extension
git add extensions/my-extension
  1. Add to extensions.toml:
[my-extension]
submodule = "extensions/my-extension"
version = "0.1.0"

# If extension is in a subdirectory:
# [my-extension]
# submodule = "extensions-my-extension"
# path = "packages/zed"
# version = "0.1.0"
  1. Sort entries:
pnpm sort-extensions
  1. Open PR to zed-industries/extensions

Updating an Extension

# Update submodule to latest commit
git submodule update --remote extensions/my-extension

# Update version in extensions.toml to match extension.toml
# Open PR with the changes

Additional Publishing Rules

  • Language/debugger extensions must NOT ship binaries — they should download or detect them
  • Theme and icon theme extensions must be published separately from language extensions
  • If an existing extension has issues, fix it upstream first before creating a new one

Extension API Reference

Key Types

Type Description
Command Process command with args and env
Worktree Project workspace (read files, find binaries, get env)
Project Zed project context
LanguageServerId LSP server identifier
ContextServerId MCP server identifier
CodeLabel Syntax-highlighted text label
SlashCommand AI assistant slash command definition
SlashCommandOutput Command response with text, sections, attachments
DebugAdapterBinary Debug adapter process configuration
DebugTaskDefinition Debug launch configuration
KeyValueStore Persistent key-value storage

Key Global Functions

Function Description
download_file(url, path) Download file (requires capability)
latest_github_release(repo, opts) Get latest GitHub release
github_release_by_tag_name(repo, tag) Get specific release by tag
npm_install_package(pkg) Install npm package
npm_package_latest_version(pkg) Get latest npm version
npm_package_installed_version(pkg) Check installed version
node_binary_path() Get Node.js binary path
make_file_executable(path) Set executable permission
current_platform() Get (OS, Architecture) tuple

Worktree Methods

Method Description
id() Worktree identifier
root_path() Project root path
read_text_file(path) Read file content
which(binary_name) Find binary in PATH
shell_env() Get shell environment variables

Common Patterns

Download + Cache Language Server

use zed_extension_api as zed;
use std::path::Path;

struct MyExtension {
    cached_path: Option<String>,
}

impl zed::Extension for MyExtension {
    fn new() -> Self {
        Self { cached_path: None }
    }

    fn language_server_command(
        &mut self,
        _id: &zed::LanguageServerId,
        worktree: &zed::Worktree,
    ) -> zed::Result<zed::Command> {
        if self.cached_path.is_none() {
            let path = self.ensure_server_binary(worktree)?;
            self.cached_path = Some(path);
        }

        let path = self.cached_path.as_ref().unwrap().clone();
        Ok(zed::Command {
            command: path,
            args: vec!["--stdio".to_string()],
            env: worktree.shell_env(),
        })
    }
}

impl MyExtension {
    fn ensure_server_binary(&self, worktree: &zed::Worktree) -> zed::Result<String> {
        let (os, arch) = zed::current_platform();
        let binary_name = format!("my-lsp-{}-{}", os, arch);
        let binary_path = format!("{}/.cache/{}", worktree.root_path(), binary_name);

        if Path::new(&binary_path).exists() {
            return Ok(binary_path);
        }

        let release = zed::latest_github_release(
            "owner/my-lsp",
            zed::GithubReleaseOptions { require_assets: true },
        )?;

        let asset = release.assets.iter()
            .find(|a| a.name.contains(&binary_name))
            .ok_or_else(|| "No matching release asset".to_string())?;

        zed::download_file(&asset.browser_download_url, &binary_path)?;
        zed::make_file_executable(&binary_path)?;

        Ok(binary_path)
    }
}

Check External Tool Availability

fn language_server_command(
    &mut self,
    _id: &zed::LanguageServerId,
    worktree: &zed::Worktree,
) -> zed::Result<zed::Command> {
    // Prefer local install, fall back to global
    let local_path = format!("{}/node_modules/.bin/typescript-language-server",
        worktree.root_path());

    let command = if Path::new(&local_path).exists() {
        local_path
    } else {
        worktree.which("typescript-language-server")
            .ok_or_else(|| "typescript-language-server not found. Install with: npm i -g typescript-language-server typescript".to_string())?
    };

    Ok(zed::Command {
        command,
        args: vec!["--stdio".to_string()],
        env: worktree.shell_env(),
    })
}

Best Practices

Extension Structure

// ✅ GOOD: Clear module organization
src/
├── lib.rs          // Main entry, register languages/themes
├── language.rs     // Language server implementation
├── theme.rs        // Color definitions
└── snippets.rs     // Snippet collections

// ❌ BAD: Everything in one file

Performance

// Cache expensive operations
fn expensive_computation(&self) -> Result<Value> {
    if let Some(cached) = &self.cached {
        return Ok(cached.clone());
    }
    // ... compute ...
}

// Lazy initialization
fn get_language(&self) -> &Language {
    self.language.get_or_init(|| /* ... */)
}

Testing

// Test extension loads correctly
#[test]
fn test_extension_loads() {
    let ext = MyExtension::new();
    assert!(ext.activate().is_ok());
}

Do:

  • Keep extension size under 1MB
  • Use async for I/O operations
  • Test on multiple Zed versions
  • Always verify crate versions on crates.io before adding dependencies — Rust's strict semver means a wrong version can cascade into hundreds of compile errors. Check zed_extension_api and any third-party crate versions explicitly
  • Inform the user when including libraries from third-party repositories and let them check versions. Highlight files and lines where you include such dependencies so the user can double-check
  • When generating Cargo.toml dependencies, mark version numbers as approximate and advise the user to run cargo check immediately to verify compatibility

Don't:

  • Block the main thread
  • Use heavy dependencies
  • Hardcode paths (use API methods)
  • Assume a crate version compiles without verifying — always recommend the user runs cargo check after dependency changes

References