Skip to content

Releases: zed-industries/zed

v0.2

13 Sep 19:14
Compare
Choose a tag to compare

With 0.2, Zed has matured to the point where we're using it regularly to write prose, and the dogfood is tasting pretty good. We knew Zed would be fast and responsive, but it's hard to understand the difference until you experience it for yourself. We can't wait to start coding in this thing!

Zed's 3 themes: Light, Dark and Black

Here's an overview of what we've added since 0.1.

Collaborative editing

You can now invite guests into any worktree you open in Zed and collaboratively edit text. We're still building out the UX around sharing and joining, but in the meantime, it's based on sharing links.

To try it out, open a folder or file in Zed and select Zed > Share from the application menu. You'll be directed to the browser to authenticate, then a link will be placed on your clipboard. Someone else can then select Zed > Join with that link on their clipboard in order to join you. They can open, edit, and save any file in the tree.

Collaboration should feel fast and stable, but for now, the experience is still pretty barebones. In Zed 0.3, we'll be replacing the link sharing workflow with a "People Panel" where you can see which of your teammates are online and start collaborating directly from within the application. We'll also be adding the ability to see which collaborators are present and follow collaborators as they move their cursor within and between files.

Chat

Our goal with Zed is to move engineering conversations closer to code, and the heart of this experience is a code-aware chat panel embedded directly into the editor. Imagine you're a new engineer on a codebase. You encounter some code that you don't understand and jump directly into the #questions channel and ask about it. Two minutes later a seasoned engineer sees your question and clicks it to jump directly to that location in your copy of the code where you can both converse and edit.

For now, the experience is very basic. You can select a channel, send and receive messages, and load from history as you scroll up. We're planning to link messages to code locations in Zed 0.3. You can't yet create your own channels, but we've added everyone to a channel named #zed-insiders so you can play with it.

Design

Zed's UI is now taking shape thanks to the design work of our new teammate Nate Butler. Our goal with Zed's design is to maximize signal and minimize noise, keeping everything as clean and simple as possible. Of course we want Zed to look good, but the content being edited should always be the main focus.

We've implemented a data-driven theming system that has allowed Nate to begin establishing a design system for Zed. It has also made it possible for him to develop three distinct color themes for Zed that can be switched at runtime: Dark, Black, and Light.

To toggle the theme selector, type cmd-k cmd-t.

Soft wrap

Soft-wrapping is essential for multi-line messages in the chat UI and a reasonable experience writing prose. It may seem simple on the surface, but it's surprisingly complex to implement correctly and even harder to make performant. We're proud of our implementation on both fronts.

We used randomized tests to ensure correctness in even the edgiest of edge cases, and we move work to the background if it ever blocks the main thread for longer than 1ms. Typical files should re-wrap instantaneously when the editor is resized or the font size changes. When editing a 25MiB JSON file, editing feels instant, and the app remains fluid and responsive when resizing thanks to our ability to move blocking work to the background. When you load the same file in VS Code, they refuse to wrap it at all.

v0.1

08 Jun 16:45
70cb755
Compare
Choose a tag to compare

Zed is starting to feel real. It’s amazing what a simple editor with syntax highlighting and advanced text editing commands can feel like when it’s screaming fast. I want this tool. I've been waiting for Zed for so long.

Screen Shot 2021-06-02 at 2 28 05 PM

This demo video of cursor movement bindings can probably give you a feel for it. To really see the performance, you’ll want to fully download the video and watch on a desktop computer rather than watching on a phone or trying to stream from Google Drive.

Trying it out

If you want to give Zed 0.1 a try, use the download link at the left.

We don’t code sign our application bundle yet, so you’ll need to jump through a hoop in order to run Zed on your Mac. After you open the DMG, rather than double-clicking the app icon, right-click and select “Open”. You’ll need to do this twice. On the second try, you’ll see the option to open the application anyway even though it isn’t signed.

Bypassing the code signing restriction

The following commands are worth playing with. Most will work with any text file, but the syntax-specific ones only work in Rust for now.

Code folding:

These are indentation-based for now, but folding based on syntax would be straightforward to add.

  • alt-cmd-[: Fold
  • alt-cmd-]: Unfold

Multi-cursor / columnar editing:

  • cmd-shift-l: Split selection into lines
  • cmd-alt-up: Columnar select up
  • cmd-alt-down: Columnar select down
  • escape: Cancel columnar editing

Line-oriented navigation and editing

  • ctrl-a: Move to beginning of line
  • ctrl-e: Move to end of line
  • cmd-l: Select line
  • ctrl-shift-k: Delete line
  • cmd-backspace: Delete to beginning of line
  • cmd-delete: Delete to end of line
  • cmd-shift-d: Duplicate line
  • ctrl-cmd-up: Move line up
  • ctrl-cmd-down: Move line down

Syntax-oriented selection

We can take this further. One idea we’d like to explore is a syntactic selection mode where the cursor is positioned on a node of the syntax tree. Up and down arrows would move up and down the syntax tree, whereas left and right would move through siblings at that level. For now we just offer a few basics that showcase our syntactic understanding.

  • alt-up: Select larger syntax node
  • alt-down: Select smaller syntax node
  • ctrl-m: Move to the enclosing / matching bracket

Technical details

Here are some highlights of the technology we’ve developed so far:

Graphics

The first thing we shipped post close was a Metal-based custom graphics backend for our UI framework. I had previously depended on a third-party graphics library called Pathfinder that was introducing complexity and an unacceptable performance overhead, so we decided to take direct control.

It was definitely a learning experience to render 2D graphics on the GPU efficiently. Signed distance fields are an important tool that’s quite fascinating… You’re programming the color of each pixel based on its distance from the perimeter of a mathematically defined shape. They worked great for rounded corners.

We’re also rasterizing Bezier curves on the GPU for our selection outlines. Here's a peek at Metal rasterizing a selection:

Screen Shot 2021-06-02 at 2 16 26 PM

Drop shadows were interesting, too, and we leaned heavily on an article by Evan Wallace, CTO of Figma, to implement them.

You may be wondering about text. Previously, we were rasterizing glyphs on the GPU with Pathfinder based on the curve data in fonts. This was overkill however, because we don’t actually need to render glyphs at arbitrary sizes and angles since we’re not trying to make Zed usable in VR. Instead, we’ve found that it’s simpler and faster to rasterize glyphs on the CPU, upload them to the GPU in an atlas texture, then texture map polygons that we place at the position of each glyph. Here's another screenshot from the Metal frame debugger. You can see how glyphs are just polygons. It's a lot like a video game, just way simpler.

Screen Shot 2021-06-02 at 2 12 21 PM

Here you can see the atlas texture to which we write all our glyphs and icons. The polygons pictured above are textured based on sub-regions within this atlas. We actually render up to 16 variants of each glyph to account for sub-pixel positioning both vertically and horizontally.

Screen Shot 2021-06-02 at 2 23 28 PM

Currently, we repaint the entire window any time anything changes. As you can see from the demo video, it's plenty fast, but we may eventually want to to explore caching layers to avoid repainting everything in order to gain more power efficiency. Compared to Electron, though, I think we're still in really good shape taking a straightforward, video-game-like approach.

The Worktree

We maintain an index of every path in the source tree the user is editing in a structure called the Worktree. It’s a copy-on-write B-tree that’s cool in a few ways. When you open a new worktree, we kick off a file system scan in the background on 16 threads. These threads contend on a lock to write new subdirectories into the tree as they crawl the file system in parallel. What’s cool about the Worktree is that its state is O(1) to clone, meaning we can periodically clone its latest state from the background workers to the UI thread every 100ms. This allows the user to start querying the paths we’ve scanned so far before we’ve finished a full scan.

The path-matching implementation is based on the Needleman-Wunsch algorithm, which I believe is German for “I wish for a needle, man”. To find that needle faster, we divide up the haystack and run matching on all of the user’s cores in parallel. Our B-tree is helpful here as well for helping us determine which part of the tree should be processed by each thread, since non-ignored file paths are unevenly distributed throughout the tree. The B-tree lets us index the count of visible files and quickly jump to a subset of those files in each thread.

Tree-sitter

We have also integrated Tree-sitter, an incremental generalized LR parser that Max Brunsfeld developed. After the user edits, Tree-sitter is able to recycle data from the previous syntax tree to produce an updated syntax tree quickly. Anecdotally, we’re seeing the majority of edits in large, complex Rust files re-parse in around 1ms, although we’ll need telemetry under real-world usage to fully understand the distribution of parsing latencies.

Parsing is asynchronous, so we never block rendering on parsing after the user types a key. We interpolate the current state based on the previous syntax tree while the parser works in a background thread to deliver the latest state.

We’ve had some interesting thoughts around selective synchronization, where we can intelligently block on data being available only so long as it won’t cause us to drop a frame. Otherwise we interpolate, and your syntax node changes color 1 frame later than the edit that caused it to become a keyword, for example. Our goal is to pack as much value in the 0-16.6ms available between a keystroke and the next frame as possible, hopefully with plenty of time to spare.

Unlike many other editors, Zed’s syntax highlighting is completely syntactically accurate, because it’s based on a parse tree from a formal grammar rather than a bunch of hacked-together regular expressions. Tree-sitter has a query system that allows you to match specific syntactic patterns, and we use it for highlights. For example, here’s how we style Rust function call expressions in Zed 0.1.

(call_expression
  function: [
    (identifier) @function
    (scoped_identifier
      name: (identifier) @function)
    (field_expression
      field: (field_identifier) @function.method)
  ])

The S-Expressions describe the patterns of a part of the syntax tree, and the @-prefixed labels give pieces of those patterns a name. In the example above, a call expression whose function is an identifier can be styled differently from a method call, where the function being called is a field_expression. We use these names directly to unify with a theme that associates the syntax decoration classes with colors, font weight, etc.

We also use the query language elsewhere. We used it to make short work of the command to move to the nearest enclosing bracket. Here’s how we express all of Rust’s bracket pairs as a query in the Rust language definition:

("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)
(closure_parameters "|" @open "|" @close)

When the user moves to enclosing bracket command, we query the tree for any matches to the above patterns that intersect the user’s selection. We then find the smallest match and ask the open and close nodes for their position in the tree. Note that we can also match identical pairs like the "s that surround strings and the |s that surround Rust closure paramete...

Read more