Surround for Zed

A Zed extension that lets you surround selected text with code snippets — inspired by vscode-surround.

Repo Rust, TypeScript, LSP server

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

  1. Select the text you want to surround
  2. Open the Code Actions menu (Ctrl+. / Cmd+.)
  3. Pick a surround operation (e.g., "Surround: try/catch")
  4. 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

  1. Open Zed
  2. Open the Extensions panel (Cmd+Shift+X)
  3. Click Install Dev Extension
  4. 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 dev builds the server and copies it to ~/Library/Application Support/Zed/extensions/work/zed-surround/server/dist/. If you only run npm 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

  1. Build the server (cd server && npm run build)
  2. Restart the language server in Zed (command palette → "language server: restart")
  3. Open a supported file (e.g. .ts, .js)
  4. 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 → ViewToggle Language Server Logs).

License

MIT