← 返回博客

Zed 周报:#25

2023年11月3日

过去几周过得跌宕起伏,我们一直在深入重写 Zed 的 UI 框架,并将整个应用程序升级到新的基础。

内森

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

GPUI 2 的出现着实令人兴奋。GPUI 的第一个版本使 Zed 成为您今天使用的产品,我们为此感到非常自豪。GPUI 1 的优点是 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 方法,然后如果编辑包含新的提及,则向用户显示一个提示。

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 方法,但它移动自身而不是借用自身。

/// 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 稳定版发布(Rust 1.75.0)时,Render 特性将看起来像这样:

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 的调度将随后进行,但每个人都投入很深,所以我们其他人将在下周跟进!

马歇尔

延续共同的主题,我本周一直在帮助推进 GPUI2 重写。这让我接触到了代码库的各个角落,也是我第一次在 GPUI2 和我们的 UI/storybook 代码之外接触 Zed。看到使用我们新的 UI 组件在真实的 Zed 工作区中渲染出第一个像素真是太棒了。

我重点关注的一个具体领域是与 Nate 一起构建新的主题系统。我们现在有了基于颜色比例构建主题的结构,可以轻松构建视觉一致的主题。最终,这个新的主题系统将使得在 Zed 中暴露更多自定义选项成为可能,而无需从头开始构建整个主题。

另外,我很高兴上周有机会参加 Zed 峰会。我很高兴能与团队的其他成员面对面交流,并一起开发 Zed 一整周!

米凯拉

过去几周过得真是疯狂!我们上周都参加了峰会,并着手进行了我们迄今为止最具雄心的项目之一:在我们开源 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 再次发文

感谢各位阅读,也感谢大家在这几周里,除了我们的代码,还和我们一起移动了我们的“原子”。很快,我们将更好地了解彼此。🙇🏻‍♂️✌️


正在寻找更好的编辑器吗?

您今天就可以在 macOS、Windows 或 Linux 上试用 Zed。立即下载


我们正在招聘!

如果您对我们博客中涵盖的主题充满热情,请考虑加入我们的团队,帮助我们实现软件开发的未来。