This Week at Zed Industries: #16
August 18th, 2023
Hey everyone!
Before the solo updates, I wanted to share an update for the company about our progress toward open source.
Earlier this summer we announced Zed's intent to go open source. How is that going? Today, we began using Zed's new channels feature internally. Once we open source, we'll use channels to stream our development and host office hours. We plan to have a channel for each of the various projects we're currently pursuing. When you tune to a channel, you can send messages, talk over audio, share your screen, all while collaboratively editing any Zed project you share into the channel.
In parallel with the channels effort, we've been fixing some pain points in our original approach to layout and styling in GPUI. We want to make building UI for Zed more approachable than its current state before we spend more time documenting it. Once that's done, we have a lot of deferred documentation to write to enable people to engage effectively with our codebase.
Now, on with your regularly scheduled weekly update:
Kyle
This was a big week for me learning gpui and understanding Zed's UI a lot better as we finished the updated Search UI. We are hopeful this offers an improvement over our current search experience, and I'm happy we got it over the line.
Beyond the Search UI, there was alot of experimentation on the AI side. First, we took a look at running cross-encoders locally on our machine with GPU acceleration on Mac. Its great to see how far performance locally has come in the space, and I think we are at a point where we will start to see local LLMs making an appearance locally on device in more applications. Along with this analysis, there was alot of exploration done in the Semantic Search engine, working to identify performance and optimization opportunities to improve the experience for our users when working on large projects. These are really challenging problems, but we've got a few creative ideas which we hope will make an impact over the coming weeks.
Antonio
Short week for me as I came back from vacation on Thursday. This week, I've been mainly thinking about how to provide first-class support for Prettier in Zed. My main thought is to provide two new options for the formatter
setting: auto
and prettier
.
The auto
option would become the default, and means that Zed will automatically try to infer how to best format your code: if a prettier config can be found, then it will use prettier, and fall back to a language server otherwise.
Users can also manually opt into always formatting things with prettier using the prettier
option. This can be configured globally, or on a per-project basis, and can be different for each language.
Nathan
My efforts on GPUI have accelerated. My goal is to make style and layout approachable to designers with front-end coding experience. This is prompting explorations into using taffy
, which I mentioned last week. Previously, we attempted to perform layout in a single recursive pass over the tree. Delegating to an external engine simplifies element code, and provides anyone with web experience a familiar styling interface. In the current design, you can even style elements by chaining methods named after Tailwind CSS classes.
Mikayla
This week, I've been experimenting with a new, composable primitive in GPUI: Components. Right now every piece of UI is written individualy from the Elements that GPUI provides for us. This is great for flexibility and we didn't want to commit to an incorrect abstraction before we'd gotten a feel for things. But now Zed has grown to the point that it's become one our largest friction when building UI. Components solve this by having a far simpler contract than either Views or Elements, and by allowing late-binding of styling information, all done in a type-safe way with the Typestate pattern.
So let's dive into an example. Note that the rest of this entry assumes a familiarity with Rust's generics and in particular it's associated types.
Here's the core of the new Component trait:
It's a simple trait that's designed around the builder pattern, note that this takes an owned self
, this keeps the lifetimes and declarations simple. You declare your state, build it up with chained method calls, and then consume the whole thing and turn it into a regular GPUI Element. Here's a simple Button
component:
And here's how you'd use it:
Our Button
struct has now encapsulated the definition of a button from the rest of the codebase. Maybe buttons can have icons, maybe there's a gradient background, no one else has to worry about exactly how it's implemented they just need to provide the correct information to create and style it.
But just having some text with a border does not a button make. Buttons often change colors on mouse hover, some are toggleable, some are disabled. We can't just keep adding methods to our Button
struct to handle all of these cases, as we'd have to recreate these fields for any other interactive or toggleable element. What we'd really like is a way to wrap a button with another component which can select the correct styles for it. To do so, we'll need a way to get at a component's styling information somehow. Let's a make a new trait to expose this information:
With this trait, we can now talk about a specific component's styling in a type safe way. here's a simple Hoverable component we can make with this:
Theres a few generics flying around, but the behavior is quite simple. We have a Hover
component, which wraps some other component C
, and applies that component's style (C::Style
) to it's own HoverStyle<C::Style>
. Let's implement this new StylableComponent
trait for our Button
:
And now we can use it in our main method like so:
Composability achieved! The implementation of the Button
is completely independent from the implementation of the Hover
, the Hover
can be applied to anything else that might be hoverable()
, and even better the implementation of Hover
is exactly as simple as it should be: a single if
.
But, we're not done yet. The button might be composable with different states, but what about the Hover
itself? What if we wanted to add a toggle state to it? Well let's try it. Here's the definition of Toggle
, similar to Hover
:
And let's add our StylableComponent
implementation and builder method:
And finally, let's try to use it:
Wait, what do we put in the '?????'? We can't put a HoverStyle
because we don't know which one we're dealing with yet. But we still have to put something in there because the compiler needs to know what type to put on the Hover
component's style
field. We could wrap this in an Option<C::Style>
, but then we'd either have to panic (wrecking type safety) or add lots of confusing branches into our rendering code. We could add a : Default
trait bound into our stylable component, analogous to the way Button::new()
sets it's border_color
to transparent black. But this wouldn't support complex styling properties like Font
, which might be wrapped in a smart pointer or be impossible to define at compile time. But all hope is not lost, because rust provides us with a type for exactly this situation: ()
.
()
, or Unit, is a special type which has exactly one value, ()
. If we parameterize our elements over their style, we can use ()
as the default value for the style, and then update our StylableComponent
trait to model the state transition from Needs a style
to A style has been provided
. Let's do that:
Note two big changes here: we took the : Component
bound off of the trait definition and moved it into the new Output
associated type. This means that StylableComponent
s don't need to be able to render themselves until after they've been styled. Here's what it looks like on the Button
component:
There's a few more generics, and a little bit more complexity, but now we are type safe, and we don't have to add a : Default
bound. Let's apply the same transformation to our Hover
component:
Again, not too different than what we had before, barring the generics. If we also apply this same transformation to Toggle
, and rework our builder helpers, we can now write our render function like this:
Now we only have to bind our styles once, as soon as we know the states a component can be in, and we can't accidentally forget to bind a style, or bind the wrong one. And the implementation of each of these components is still exactly as simple as it should be.
That said, the error messages can get a bit hairy if you do forget to bind a style:
But that's what pair programming is for, right? :)
Conrad
I am focused on improving Vim emulation. The major new feature this week is Visual Block Mode! We slightly improve on the original thanks to Zed's excellent multi-cursor support (so you're not just limited to inserting new text). To make this work well we completely overhauled the way Vim selections work, and along the way fixed a bunch of other small bugs in the existing visual modes.
Kirill
Two big and very different things happened concurrently for me this week:
- tailwind language server experiments
I've got a great chance to pair with Julia on completions and a new language server, test Zed on a new JavaScript project and get very amused at how differently language servers can be implemented, albeit using the same protocol. We have the completions working in general, but the devil is in the details and those are abundant at the moment, let's see when we get over those.
- inlay hint hover
I have the code to derermine which inlay hint segment is hovered, which required considering in various coordinate spaces of the text in the editor and was an interesting refresher of what was written during the initial inlay hint implementation. Now I work on the resolve part, presumably the last big task before I can glue it all together and make it work.
Between dealing with the two projects, I've managed to fix a couple papercut bugs with terminal shortcuts and a few other small things.
Piotr
This week I have continued poking at search implementation and new search UI. Soon you'll experience a smoother search experience. In the meantime I have also started looking back at the PHP implementation which has quite a few rough edges. In the next week I will continue working on Semantic Search engine with Kyle.
Joseph
I'm back to working on the Python tool that pulls all feedback from multiple sources into a single application. Ideally, it will be able to pull in data from GitHub Issues, PostgreSQL (our in-app feedback), email, and anything else we want. I'm in the middle of writing the backend code. The idea is that there's a DataStoreManager
and various types of DataStore
s that you can configure and register with it. The stores implement their own methods to pull fresh data down from their respective sources. The manager runs these methods as tasks. After completion, the data is serialized and stored in a SQLite database, so we don't have to pull the same data each time we run it. Once I get this working, I'll start working on the frontend and trying introducing some sort of AI to make reports. I intend for this to be a TUI application; I'm using Textual. I have quite a bit of work ahead of me, but it's fun to be writing some 🐍 again.
Nate
I've been supporting the internal launch of channels, and helping navigate some of the complexities of working with our theme when build UI for GPUI. A lot of this may change in the near future, as per Nathan's updates the past few weeks. I'm excited to start using channels internally, resolve soem of the pain points, and then get them out to the world.
Julia
Been spending a lot of my time this week collaborating and coordinating with others, primarily on our efforts to get Tailwind up and running in Zed. Kirill already went into some good detail so it'll suffice to say I'm excited to get this oft requested feature in the hands of our users.
Max
This week, our team started using an initial version of our new channels feature, which makes it easier to set up and join Zed calls, without having to invite every participant. It's already been fun to pop into different channels for quick conversations with different teammates.