← 返回博客

Zed Weekly: #25

2023年11月3日


过去几周有点疯狂,我们一直在深入重写 Zed 的 UI 框架,并将整个应用程序升级到新的基础上。

Nathan

为什么现在?毕竟,我们不应该添加对 X、Y、Z 的支持吗?他们称之为软件开发是有原因的。一个只会增长的代码库不能真正地发展,有时革命是必要的。在这种情况下,我们希望在开源 Zed 之前完成这场革命。

GPUI 2 正在涌现的东西真的令人兴奋。GPUI 的第一个版本已经使 Zed 成为你今天使用的产品,我们对此感到非常自豪。GPUI 1 的优势是使 Zed 成为今天 Zed 的重要组成部分。但是 GPUI 1 的弱点阻碍了 Zed 的发展。

GPUI 2 延续了第一个版本的性能和可靠性优势,同时从根本上改善了降低我们速度的人体工程学问题。任何 Zed 的贡献者都需要学习这个框架,因此在人体工程学方面的投资回报很高。

我将分享一些关键的想法。

在 GPUI 中,所有应用程序状态都由一个名为 AppContext 的单个对象拥有。模型是一种状态。给定一个 AppContext,你可以按如下方式创建一个模型

let cx: &mut AppContext = todo!("keep reading");
cx.new_model(|cx| Document {
    path: "/journal/2023/11/3.md".into(),
    text: "I'm tired, but also inspired!"
})

将此打包在构造函数中会很有帮助,如下所示

struct Document {
    pub fn new(path: Option<SharedPath>, cx: &mut AppContext) -> Model<Document> {
        cx.new_model(|cx| {
            let text = Rope::new(cx);
            text.subscribe(cx, |this, text, edit: &Patch<u32>, cx| {
                for mention in self.scan_mentions(text, edit, cx) {
                    cx.toast(mention);
                }
            });
            Self { path, text }
        )
    }
}

在上面的代码中,当我们创建一个文档时,我们创建一个 rope 来存储其文本,该文本也是一个模型,具有类似的构造函数,它接受一个上下文。然后我们订阅文本上的编辑事件,这些事件表示为“补丁”。我们调用 scan_mentions 方法,如果编辑包含新的提及,则向用户显示toast。

Model<Document> 在某些方面类似于 Rust 附带的标准引用类型,例如 BoxRc。区别在于模型存储在 AppContext 中,如果你眯着眼看,它有点像特定于应用程序的堆。要解引用 Model<T>,你需要传递一个拥有其状态的上下文。

当模型被构造或更新时,它们可以与此上下文交互以发出和订阅事件,通知其他模型它们已更改,并访问其他应用程序范围的 API。

除了 Model<T> 之外,还有 View<T>。视图包装模型,但它们强制执行 T 实现 Render 特征,该特征具有将 T 中的状态映射到元素树的单一方法。

pub struct View<V: Render> {
    pub(crate) model: Model<V>,
}
 
pub trait Render: 'static + Sized {
    type Element: Element<Self> + 'static;
 
    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
}

实际上,render 实际上需要返回任何 impl Component 的东西。Component 是任何可以转换为元素树的类型。它还有一个 render 方法,但它移动 self 而不是将 self 作为借用。

/// The core stateless component trait, simply rendering an element tree
pub trait Component {
    fn render<V: 'static>(self, cx: &mut ViewContext<V>) -> AnyElement<V>;
}

impl Trait 在 traits 中达到稳定版(Rust 1.75.0)时,Render trait 将如下所示

pub trait Render: 'static + Sized {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component;
}

😽🍝 感谢 Rust 语言开发者。

最后,这是一个完整的,如果最小的 GPUI 2 应用程序的示例。

struct Hello {
    user: SharedString,
}
 
impl Render for Hello {
    type Element = Div<Self>;
 
    //
    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
        let color = cx.theme().colors();
        div()
            // Flex properties
            .flex()
            .flex_col()
            .gap_2()
            // Size properties
            .w_96()
            .p_4()
            // Set width to 384px
            // Add 16px of padding on all sides
            // Color properties
            .bg(color.surface)
            // Set background color
            .text_color(color.text) // Set text color
            // Border properties
            .rounded_md()
            // Add 4px of border radius
            .border()
            // Add a 1px border
            .border_color(color.border)
            .child(format!("Hello, {}!", self.user))
    }
}
 
impl Hello {
    fn new(cx: &mut WindowContext) -> View<Self> {
        cx.build_view(|_| Hello {
            user: "world".into(),
        })
    }
}
 
fn main2() {
    gpui2::App::production(Arc::new(Assets)).run(|cx| {
        settings::init(cx);
        theme::init(cx);
        cx.open_window(WindowOptions::default(), |cx| Hello::new(cx));
    });
}

这种转变非常激烈。有点像以最高速度将航空母舰向右转。我们认为这是值得的。

来自 Marshall 和 Mikayla 的消息将在稍后发布,但每个人都非常深入,所以我们其他人下周再见!

Marshall

与常见主题保持一致,我本周花时间帮助推进 GPUI2 的重写。这让我跑遍了整个代码库,并且是我第一次接触 GPUI2 和我们的 UI/storybook 代码之外的 Zed。看到使用我们在真实的 Zed 工作区中的新 UI 组件渲染的第一个像素真是太棒了。

我关注的一个特定领域是与 Nate 一起构建新的主题系统。我们现在有了用于构建基于颜色刻度的主题的构造,从而可以轻松构建视觉上一致的主题。最终,这个新的主题系统将使我们能够为 Zed 公开更多的自定义选项,而无需从头开始构建整个主题。

另一方面,我很高兴有机会参加上周的 Zed 峰会。我玩得很开心,有机会与团队的其他成员见面,并在整个一周内一起破解 Zed!

Mikayla

这真是疯狂的几周!我们上周举行了峰会,并且开始了我们迄今为止最雄心勃勃的项目之一:在我们开源 Zed 之前,用我们一直称之为“GPUI2”的东西重写整个应用程序。GPUI2 是我们内部 UI 框架的重写,允许我们编写我们可以实际使用的可组合 UI 代码。我们有一个新的布局引擎,一个时髦的新执行器,以及一堆基于 tailwind CSS 库的样式助手。

对我来说,我一直专注于学习新系统并帮助构建 GPUI2 需要但尚未连接的功能。这真是一次疯狂的旅程,因为几乎每天都揭示了我们的应用程序所依赖的 GPUI2 的问题。Rust 的类型系统使这种重构成为可能,我们都花了很多时间重命名函数并交换新的类型。

我们在依赖关系图中有几个大的 crate

  • project,它将我们的核心应用程序代码连接在一起
  • workspace,它将项目与我们的所有其他 UI 相结合
  • editor,它为 Zed 中的每个文本输入提供支持。

由于这些瓶颈,重新键入 Zed 比我们希望的更加同步,这对我们大多数时间都在远程工作的 10 人团队来说是个坏消息。值得庆幸的是,我们有一个每天使用的用于同步协作的强大工具:Zed!我们在峰会上度过了一些令人难以置信的时刻,当时有 4 个人坐在会议桌旁,都从他们自己的笔记本电脑上在一个人机器上工作,流畅地分离和重新加入以处理构建图的不同部分,因为它们可用了。

自峰会以来,这种做法一直在继续,有 2 或 3 个团队在他们可以并行工作的地方并行工作。如果没有 Zed 的同步编程能力和 Rust 的类型系统的指导,这将是一次更加艰难的旅程。

现在,经过我们第二周的重写,我们已经完成了大约 1/3-1/2 的应用程序,并且已经达到了大规模可并行部分,希望我们很快就能发布新的 Zed 预览版!

Nathan 再次

感谢大家的阅读,并感谢大家在我们过去几周移动我们的原子和比特的过程中对我们的支持。很快我们就会彼此更加了解。🙇🏻‍♂️✌️