← 返回博客

Zed 周报:#27

2023 年 11 月 24 日

内森

在 Mikayla 和我周末投入时间,以及 Conrad 帮助我们合并结果后,我们终于为 GPUI 的核心视图相关 trait 找到了一个令人满意的设计。

在 GPUI 中,当你打开一个窗口时,你会提供一个 视图,它指示该窗口应该显示什么。要实现一个视图,你需要在一个类型上实现 Render trait,以描述它如何在屏幕上显示。

pub trait Render: 'static + Sized {
    type Rendered: RenderOnce + 'static; // We can delete this type in Rust 1.75
 
    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Rendered;
}

例如,你可以像这样制作一个简单的 TaskList 视图。

use gpui::{prelude::*, Div, div};
 
struct TaskList {
    title: SharedString,
    tasks: Vec<Task>
}
 
struct Task {
    title: SharedString,
    completed: SharedString,
}
 
impl Render for TaskList {
    type Rendered = Div;
 
    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Rendered {
        // In reality this would need more styling.
        // This example is focused on data flow.
        div()
            .child(self.title.clone())
            .children(self.tasks.iter().map(|task| {
                div()
                    .flex()
                    .flex_row()
                    .child(checkbox(task.completed))
                    .child(task.title.clone())
            }))
    }
}

Render trait 的一个兄弟是 RenderOnce,它通过移动对象来渲染。

pub trait RenderOnce: Sized {
    type Element: IntoElement;
 
    fn render(self) -> Self::Element;
}

通常,你会使用这个 trait 来实现可以由其他元素组合而成的自定义 UI 元素,例如:

#[derive(IntoElement)]
struct Button {
    title: SharedString,
    icon_path: Option<SharedString>,
    on_click: ClickListener,
}
 
impl RenderOnce for Button {
    type Rendered = Div;
 
    fn render(self, cx: &mut WindowContext) -> Self::Rendered {
        div()
            .flex()
            .flex_row()
            .child(self.title.clone())
            .children(self
                .icon_path
                .clone().map(|path| svg(path)))
    }
}

请注意 Button 上的 #[derive(IntoElement)] 属性宏。这会自动派生 IntoElement trait,这对于将任意子元素传递给父元素(如 div)非常有用。

// Note that you don't need to call `Button::render` here.
div().child(Button::new("button"))

内特

祝美国的朋友们感恩节快乐!我们加拿大其实在十月庆祝感恩节,所以我还在努力工作 😄

我想谈谈 UI 缩放以及它如何融入我们编写 gpui2 UI 的方式。

我们采用了 rem 单位和 px,以便更容易地缩放 UI 元素。rem 是一种测量单位,它相对于 CSS 中根元素的字体大小。

在 gpui2 中,我们同样将 1 rem 的大小视为 ui_font_size 的大小,这是我们添加到应用程序中的一个新设置。它默认为 16 (16px),这是大多数浏览器中的默认字体大小,并且可以很好地划分到基于 4 的网格中。

许多 UI 库和设计系统都使用基于 4 的网格,因为尺寸的自然演变易于使用。

一个元素可能有:- 4 像素的内边距 - 4 像素的边框半径 - 12 或 16 像素的字体大小 - 如果它包含多个元素,它们之间可能间隔 4 像素

这使我们可以在构建时像使用像素一样,但通过更改 ui_font_size 设置来放大或缩小 UI。

然而,我们仍有一些问题需要解决。0.25px 到底意味着什么?当你进入非常小的 UI 尺寸时,你会开始遇到我们仍在解决的问题。

UI Scale example - 14px/16px/20px
UI 缩放示例 - 14px/16px/20px

上面是 ui_font_size 设置为 14px、16px 和 20px 的示例。UI 缩放可以独立于 buffer_font_size 进行设置,因此你可以拥有一个带有小字体大小的大 UI,或者反之。

Zed 的可伸缩 UI 仍处于早期阶段,因此仍有一些粗糙之处。

基里尔

本周,我专注于修复现有错误并推动 Zed 的工作树文件功能。现在,设置中有一个 file_scan_exclusions 列表,默认情况下会隐藏 Zed 中的许多 .DS_Store-like 文件。

随着排除项的发布,工作树似乎只剩下一个突出的问题,这与排除项恰好相反:在 gitignore 文件层次结构中的各种查找。

  • 项目面板(文件树)已经处理了 gitignore 文件,将它们灰显,并且只有当它们在文件树中“打开”时才(重新)加载 gitignore 目录。

  • 项目搜索无法在 gitignore 文件中搜索,直到本周我解决了这部分。在实现了排除部分之后,这部分相对容易启用,但性能部分还有一些唾手可得的改进。任何普通的 node_modules 或 target 都可能因短查询而结果过多,因此目前尚不清楚此处应如何划定基线。

  • 文件查找器(打开文件面板)也不会匹配 gitignore 文件路径,这似乎是最后一个会因此而大幅受益的元素。然而,这似乎是所有问题中最复杂的一个:目前,Zed 既不急切扫描 gitignore 目录,也不跟踪它们的 FS 事件:node_modulestarget 目录可能比常规项目大得多,这似乎非常浪费。事实上,我们以前做过类似的事情,这对许多用户的工作负载来说是相当繁重的。这个“面板”本身是一个简单的输入字段,没有额外的过滤器,似乎最多只能添加一个“所有 gitignore 文件或不包含任何文件”的切换,并且由于输入查询是一个模糊匹配字符串,这意味着 Zed 必须搜索所有 gitignore 目录的根目录直到末尾才能正确匹配结果。如果能早早达到最大匹配路径限制,那将是幸运的,但如果平均体验是等待几秒钟才能生成不到 100 条额外路径行,那就不够好了。为某些 gitignore 目录添加额外设置以包含它们似乎很复杂(我们已经有排除项了!),而且不够灵活。我仍在不确定中权衡利弊,试图为该功能找到一个好的用户体验。

最后,为了至少以某种方式帮助 Zed2,我参与了 Zed 夜间发布的基础工作,帮助 Mikayla 为基于 gpui2 构建的 Zed 启动工作。


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

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


我们正在招聘!

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