Releases: zed-industries/zed
v0.4
v0.3.1
- Sped up the login process
- Sped up the process of starting to share a directory
- Changed word-boundary cursor movement commands to be more consistent with other text editors (#169)
- Fixed a bug where extra characters were inserted when pasting non-ascii text (#156)
- Fixed a crash when typing while dragging a selection with the mouse (#155)
v0.3
We now have a new UI for initiating collaboration.
To grant other users access to a source tree, you'll need to create a .zed.toml
file in the root that lists their GitHub usernames under the collaborators
key:
collaborators = ["nathansobo", "as-cii", "maxbrunsfeld"]
You'll then need to authenticate by clicking the avatar icon in the upper right corner of the window. Once you do this, anyone listed as a collaborator in any of your open folders will see you in the people panel.
To start sharing, open your people panel and click the folder you want to share. To join another user's shared folder, simply click it in the people panel. Your avatar will then appear next to it and you'll be able to open files from the remote folder.
Any user you add to the collaborators list will be granted access to download Zed from https://zed.dev if they don't have access already. For now, we only honor the contents of the .zed.toml
file when the folder is first opened in Zed. We'll follow up with a release that allows you to change the collaborators on the fly.
v0.2.2
- Only the local cursor blinks when collaborating
- Apply syntax highlighting to new files when saved
- Fix a bug that could cause Zed to hang when collaborating
v0.2.1
- Sign in status is now displayed in the titlebar
- You now can click the signed out icon in the titlebar to authenticate
- Connection status is now displayed in the titlebar
- Fixed a panic that could occur during momentary connection loss
- Improved logging of panics
v0.2
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!
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
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.
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.
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-[
: Foldalt-cmd-]
: Unfold
Multi-cursor / columnar editing:
cmd-shift-l
: Split selection into linescmd-alt-up
: Columnar select upcmd-alt-down
: Columnar select downescape
: Cancel columnar editing
Line-oriented navigation and editing
ctrl-a
: Move to beginning of linectrl-e
: Move to end of linecmd-l
: Select linectrl-shift-k
: Delete linecmd-backspace
: Delete to beginning of linecmd-delete
: Delete to end of linecmd-shift-d
: Duplicate linectrl-cmd-up
: Move line upctrl-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 nodealt-down
: Select smaller syntax nodectrl-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:
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.
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.
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...