-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Description:
Implement mtlog-analyzer support as a Zed extension. This would be the 4th editor integration alongside VS Code, GoLand, and Neovim.
Background:
Zed's extension system uses Rust compiled to WebAssembly. Extensions run sandboxed and communicate via LSP. Unlike other editors, Zed requires a persistent LSP server rather than one-shot command execution, so we'll create mtlog-lsp as an LSP wrapper with bundled analyzer.
Architecture
mtlog-lsp (Bundled LSP Server)
A unified component that provides LSP functionality with integrated analyzer:
- Bundles mtlog-analyzer directly in the binary for zero subprocess overhead
- Stays running as a persistent LSP server
- Runs analyzer using
packages.Loadandanalysis.Passinternally - Handles byte offset to line/character position conversion
- Manages diagnostics and code actions separately to avoid conflicts with gopls
Installation (single binary):
go install github.com/willibrandon/mtlog/cmd/mtlog-lsp@latestImplementation
Extension Structure
zed-extension/mtlog/
├── Cargo.toml
├── extension.toml
├── src/
│ └── lib.rs
└── README.md
Core Implementation
use zed_extension_api::{self as zed, Command, Extension, LanguageServerId, Result};
struct MtlogAnalyzerExtension {
cached_binary_path: Option<String>,
}
impl Extension for MtlogAnalyzerExtension {
fn new() -> Self {
Self {
cached_binary_path: None,
}
}
fn language_server_command(
&mut self,
_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<Command> {
let binary_path = self.cached_binary_path.get_or_insert_with(|| {
find_mtlog_lsp().unwrap_or_else(|| "mtlog-lsp".to_string())
});
Ok(Command {
command: binary_path.clone(),
args: vec![], // mtlog-lsp doesn't need arguments
env: Default::default(),
})
}
}Binary Detection
fn find_mtlog_lsp() -> Option<String> {
// First check if in PATH
if let Some(path) = worktree.which("mtlog-lsp") {
return Some(path);
}
let paths = vec![
std::env::var("GOBIN").ok(),
std::env::var("GOPATH").ok().map(|p| format!("{}/bin", p)),
Some(format!("{}/go/bin", std::env::var("HOME").ok()?)),
];
for path in paths.into_iter().flatten() {
let binary = format!("{}/mtlog-lsp", path);
if std::fs::metadata(&binary).is_ok() {
return Some(binary);
}
}
None
}Configuration Files
extension.toml:
id = "mtlog-analyzer"
name = "mtlog-analyzer"
version = "0.1.0"
schema_version = 1
authors = ["Brandon Williams"]
description = "Static analysis for mtlog message templates in Go"
repository = "https://github.com/willibrandon/mtlog"
[language_servers.mtlog-analyzer]
name = "mtlog-analyzer"
languages = ["Go"]Cargo.toml:
[package]
name = "zed-mtlog-analyzer"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.2.0"Features
- All MTLOG001-MTLOG013 diagnostics via LSP
- Quick fixes/Code Actions work properly through LSP code actions
- Automatic binary detection for mtlog-lsp in standard Go paths
- Installation instructions when mtlog-lsp not found
- Proper byte offset conversion for accurate text edit positions
- Configuration support for analyzer flags (passed through initialization_options)
- Diagnostic suppression via workspace configuration actions
Requirements
Only one component needed (bundled analyzer):
go install github.com/willibrandon/mtlog/cmd/mtlog-lsp@latestThe mtlog-lsp binary will include the analyzer bundled directly, eliminating:
- Subprocess spawning overhead
- Binary detection complexity for analyzer
- Process communication latency
- Dual installation requirements
Publishing
Initial Publishing
- Fork zed-industries/extensions
- Add extension as submodule
- Update
extensions.tomlwith entry - Submit PR to zed-industries/extensions
Automated Updates
Since mtlog releases all components together, we'll automate Zed extension updates:
Release Script (scripts/release-zed-extension.sh):
#!/bin/bash
# Automatically update Zed extension after mtlog release
set -e
VERSION=$1
COMMIT=$2
EXTENSIONS_DIR="$HOME/zed-extensions-fork"
if [ -z "$VERSION" ] || [ -z "$COMMIT" ]; then
echo "Usage: $0 <version> <commit>"
exit 1
fi
cd "$EXTENSIONS_DIR"
git checkout main
git pull upstream main
# Update submodule
cd extensions/mtlog-analyzer
git fetch
git checkout "$COMMIT"
cd ../..
# Update version
sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" extensions.toml
rm extensions.toml.bak
# Create branch and PR
BRANCH="update-mtlog-analyzer-$VERSION"
git checkout -b "$BRANCH"
git add .
git commit -m "Update mtlog-analyzer to $VERSION"
git push origin "$BRANCH"
# Create PR using GitHub CLI
gh pr create \
--repo zed-industries/extensions \
--title "Update mtlog-analyzer to $VERSION" \
--body "Release: https://github.com/willibrandon/mtlog/releases/tag/$VERSION"
echo "✓ PR created"GitHub Actions Integration (.github/workflows/release.yml):
- name: Update Zed Extension
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Setup: ensure fork exists
gh repo fork zed-industries/extensions --clone=false || true
# Clone/update fork
git clone https://github.com/${{ github.actor }}/extensions.git zed-extensions-fork
cd zed-extensions-fork
git remote add upstream https://github.com/zed-industries/extensions
# Run update
../scripts/release-zed-extension.sh \
"${{ github.event.release.tag_name }}" \
"${{ github.sha }}"This automation approach:
- Runs automatically on release
- Uses simple shell scripts (no complex dependencies)
- Easy to test locally
- Easy to debug if issues arise
- ~30 lines of maintainable code
Testing Requirements
- Verify diagnostics display correctly
- Test quick fix/code action application
- Validate binary detection on macOS/Linux
- Check performance impact (<10ms added latency)
- Test with go.mod projects and standalone files
- Verify mtlog-lsp stays running and handles multiple requests
- Test that code actions apply at correct positions
- Verify bundled analyzer performance matches standalone
Implementation Notes
- Zed requires a persistent LSP server (not one-shot execution)
- mtlog-lsp will bundle the analyzer directly using
packages.LoadAPI - Extension only needs to provide command to launch mtlog-lsp
- No custom UI needed - Zed's built-in diagnostics panel works
- Quick fixes work through standard LSP code actions
- Diagnostics are published without Data field to avoid gopls conflicts
- Configuration passed via initialization_options from Zed settings
Comparison with Other Editor Integrations
| Editor | Language | LOC | Notes |
|---|---|---|---|
| VS Code | TypeScript | ~3,700 | Custom diagnostics provider, quick fixes, configuration UI |
| GoLand | Kotlin | ~7,200 | IntelliJ platform integration, external annotator, custom UI |
| Neovim | Lua | ~14,200 | Complete LSP client implementation, caching, async handling |
| Zed | Rust | ~200 | Simple LSP wrapper launching mtlog-lsp |
| mtlog-lsp | Go | ~750 | LSP server with bundled analyzer (required for Zed) |
Zed requires minimal extension code because it delegates entirely to its built-in LSP infrastructure. The bundled approach will eliminate subprocess overhead and simplify installation to a single binary.