Surround for Zed
A Zed extension that lets you surround selected text with code snippets — inspired by vscode-surround.
A Zed editor extension that brings the vscode-surround experience to Zed. Select text, trigger the command, and wrap it with your choice of snippet — brackets, tags, functions, and more.
Project README
zed-surround
A Zed extension that lets you surround selected text with code snippets — inspired by vscode-surround.
Usage
- Select the text you want to surround
- Open the Code Actions menu (
Ctrl+./Cmd+.) - Pick a surround operation (e.g., "Surround: try/catch")
- Edit any placeholder values (
condition,name,params, etc.)
Available Surrounds
Control Flow
| Action | Result |
|---|---|
| Surround: if | if (condition) { ... } |
| Surround: if/else | if (condition) { ... } else { } |
| Surround: try/catch | try { ... } catch (error) { } |
| Surround: try/finally | try { ... } finally { } |
| Surround: try/catch/finally | try { ... } catch (error) { } finally { } |
Loops
| Action | Result |
|---|---|
| Surround: for...of | for (const item of iterable) { ... } |
| Surround: for (indexed) | for (let i = 0; i < length; i++) { ... } |
| Surround: forEach | items.forEach((item) => { ... }) |
| Surround: forEach (async) | items.forEach(async (item) => { ... }) |
| Surround: forEach (function) | items.forEach(function (item) { ... }) |
| Surround: forEach (async function) | items.forEach(async function (item) { ... }) |
Functions
| Action | Result |
|---|---|
| Surround: arrow function | const name = (params) => { ... } |
| Surround: async arrow function | const name = async (params) => { ... } |
| Surround: function declaration | function name(params) { ... } |
| Surround: async function declaration | async function name(params) { ... } |
| Surround: function expression | const name = function (params) { ... } |
| Surround: async function expression | const name = async function (params) { ... } |
| Surround: IIFE | (function (params) { ... })(args) |
Markup (HTML/JSX only)
| Action | Result |
|---|---|
| Surround: HTML element | <element>...</element> |
Other
| Action | Result |
|---|---|
| Surround: block comment | /* ... */ |
| Surround: #region | // #region name ... // #endregion |
| Surround: template literal | `...` |
| Surround: template literal variable | `${...}` |
| Surround: console.log | console.log(...) |
Custom Surrounds
You can add your own surrounds, override built-ins, or disable ones you don't use via Zed's settings. Configure globally in ~/.config/zed/settings.json or per-project in .zed/settings.json:
{
"lsp": {
"surround-ls": {
"settings": {
"surrounds": [
{
"name": "myWrapper",
"label": "Surround: My Wrapper",
"template": "wrapper({\\n\\t{{selected}}\\n})",
"languages": ["typescript", "javascript"]
}
],
"overrides": {
"consoleLog": {
"template": "console.warn({{selected}});"
}
},
"disabled": ["iife", "region"]
}
}
}
}
Options
| Key | Description |
|---|---|
surrounds |
Array of custom surround templates to add |
overrides |
Override built-in templates by name (see table above for names) |
disabled |
Array of built-in names to hide |
Template syntax
| Token | Meaning |
|---|---|
{{selected}} |
Replaced with the selected text (required) |
\n |
Newline |
\t |
One indent level (auto-detected from your file) |
Example: adding a custom surround
{
"name": "describe",
"label": "Surround: describe block",
"template": "describe('name', () => {\\n\\t{{selected}}\\n});",
"languages": ["typescript", "javascript", "typescriptreact", "javascriptreact"]
}
The languages field is optional — omit it to make the surround available in all languages.
Supported Languages
JavaScript, TypeScript, TSX, JSX, Python, Rust, Go, C, C++, Java, Ruby, PHP, C#, Swift, Kotlin, Dart, HTML, CSS, SCSS, Vue, Svelte, Markdown, JSON, YAML, TOML, Shell Script, Elixir, Lua, Zig
How It Works
This extension implements a lightweight LSP (Language Server Protocol) server that provides Code Actions. When you select text and open the Code Actions menu, the server generates surround options that wrap your selection with the chosen template while preserving indentation.
Development
Prerequisites
Setup
# Install dependencies and build the language server
cd server
npm install
npm run build
Install as a dev extension
- Open Zed
- Open the Extensions panel (
Cmd+Shift+X) - Click Install Dev Extension
- Select this repo's root directory
Dev workflow
After making changes to the server, build and copy it to Zed's extension directory in one step:
cd server
npm run dev
Then in Zed, run "language server: restart" from the command palette (Cmd+Shift+P).
Note: Zed extensions run from a sandboxed work directory, not your source repo.
npm run devbuilds the server and copies it to~/Library/Application Support/Zed/extensions/work/zed-surround/server/dist/. If you only runnpm run build, the language server won't pick up your changes.
Project structure
├── extension.toml # Zed extension manifest
├── src/
│ └── surround.rs # Zed extension glue (launches the LSP binary)
└── server/
└── src/
├── server.ts # LSP server (code actions, config loading)
├── surrounds.ts # Built-in surround templates
└── config.ts # User config types, validation, and merging
Testing changes
- Build the server (
cd server && npm run build) - Restart the language server in Zed (command palette → "language server: restart")
- Open a supported file (e.g.
.ts,.js) - Select some text and open Code Actions (
Cmd+.) — you should see the surround options
To test custom surrounds, add config to .zed/settings.json in your test project:
{
"lsp": {
"surround-ls": {
"settings": {
"surrounds": [
{
"name": "test",
"label": "Surround: test wrapper",
"template": "test({\\n\\t{{selected}}\\n})"
}
]
}
}
}
}
Check the LSP logs for warnings about invalid config (Zed menu → View → Toggle Language Server Logs).
License
MIT