← 返回博客

本周 Zed Industries:第 15 期

2023 年 8 月 11 日

欢迎来到第十五期《本周 Zed Industries》!时光飞逝。随着夏季进入尾声,我们正在逼近内部 Channels Alpha,进一步调整性能,并添加了一些新的文本操作命令。让我们深入了解一下!

Piotr

本周我专注于对项目和缓冲区搜索进行进一步的样式调整。为此,我还与 Julia 合作,调查了项目搜索的各种性能怪癖。我们发现的一个显著怪癖是,在搜索过程中偶尔会出现短暂的卡顿,伴随着“正在搜索...”的标签持续几秒钟。正如我们发现的,Zed 只有在完全完成搜索后才会显示搜索结果。天哪!在过去的一周里,我们重写了项目搜索例程的本地部分,以便在获得搜索结果后立即报告。下周我计划继续与 Julia 共同研究我们的搜索实现,并利用新获得的见解加快其他类型的搜索。同时优化代码和社交真是太棒了!

茱莉亚 (Julia)

我本周大部分时间都花在继续尝试 WASM 可扩展性上,目的是启用自定义语言服务器。我们仍然不清楚要如何推进,但我们正在学习很多东西,并就这个话题进行了大量的内部讨论。

我的其余时间都和 Piotr 一起致力于加快我们的项目搜索,这非常有趣且令人满足。我一直很喜欢改进性能的工作,并且在此过程中更好地了解了我的同事,这很棒。

约瑟夫

过去几周我很高兴能多写点代码。在 Zed 中,我一直专注于添加行和文本操作命令,因为自从全职切换到 Zed 后,我就一直很怀念这些命令。周末,我通过添加大小写转换(转换为 kebab case转换为 upper camel case 等)来完善文本操作命令。我对我们目前的文本操作命令感到满意——这是一个快速演示

各种文本操作命令的演示

我花了一些时间重新编写了我们的热门问题脚本,因为它的原始编写方式效率非常低,并且使用的请求数量与跟踪器中的问题数量呈线性增长;由于我们达到了 GitHub 的速率限制,脚本经常失败。现在,该脚本只需几秒钟即可运行,并且应该消耗恒定数量的请求。

最后,我开始开发一个实验性工具,一个 TUI 应用程序,用于将来自各种渠道的所有反馈集中到一个位置。几个月前我们也开发过一个类似的工具,但由于其基于网络的性质导致各种问题而被放弃了。有机会用 Python 工作总是很有趣的。

基里尔

这周中旬我休假回来了,所以这次没什么可分享的。

首先,我决定暂时搁置文件拖放的尝试:终端中的 cmd-click 已经提供了 80% 的功能,而且在我离开期间,一些任务变得更加重要。

说到其中一个,内联提示的性能并没有达到预期的可扩展性——Zed 整体的流畅度将标准设定得很高。我正在研究最初的想法之一,更严格地跟踪我们查询提示的编辑器范围。这让我在考虑未完成的提示实现博文时感觉好一点:有些事情肯定会彻底改变。

我还添加了一个用于切换提示和缓冲区搜索的小面板,这应该有助于功能发现和这些切换的快捷键。鉴于提示方面又做了大量工作,我开始思考在有动力的时候,如何以“简单”的方式实现动态提示功能...

内特

今天的简短更新——上周我刚休假一周回来。这周大部分时间都在和 Mikayla 一起研究频道,深入了解实现细节,并理解如何让通用组件更好地适应 Rust 的细微差别。此外,我还支持了 Nathan 的主题探索,以及其他一些杂项。

米凯拉

本周,我们来谈谈 Channels 安全模型以及我在撰写上周文章时发现的一个严重权限提升漏洞。这个系统中有一个额外的规则,我曾认为它是如此不言而喻,以至于我忘记提及:当你创建一个新的根 Channel 时,你会自动获得该 Channel 的管理员角色。为了说明这个问题,请看我给出的个人重组示例,并标注了我的成员角色

- #my-freelance-design-business - (Admin)
  - > #crdb-industries/#design  - (Member)
  - > #zed/#design              - (Member)

借助级联角色系统,我在我自己的私人组织中的管理员角色覆盖了 #crdb-industries#zed 授予我的成员角色,这是一个微不足道且无法击败的权限提升漏洞。

为了纠正这个明显的错误,我匆忙地在句子发布后立即添加了“with subtractive interference”的字样。

An image of a GitHub commit diff, showing the addition
一张 GitHub 提交差异的图片,显示了添加的内容

但这个修复却制造了完全相同的问题,只是方向相反!如果我将我的一个同事添加为 #my-freelance-design-business 的成员,他们就会失去对他们自己组织的频道拥有的管理员角色!

这里根本的问题在于,我们正在使用的安全模型,即访问控制列表(ACL),无法以我们所需的方式组合在一起,以允许我上周描述的那种行为。值得庆幸的是,存在另一种模型可以做到:对象能力(OCaps)。自从我接触到 Spritely Institute 及其基于 Lisp 的 Goblins 框架以来,我就一直对 OCaps 感到兴奋。如果您想深入了解对象能力并在此过程中学习一些 Lisp,他们的 Heart of Spritely 论文有详细介绍。

就我们的目的而言,对象能力归结为一个简单的规则:访问即授权。如果你有权访问某个东西,那么你就可以做该访问允许你做的任何事情。请注意权限记录所在的位置,它不在个人身上,也不在服务器上,而是在**访问本身**。现在,我上面描述的问题就迎刃而解了。与其使用角色,不如想象一下可以在频道上执行的操作,读取和写入,它们对应于上面的成员和管理员角色,然后我们取消级联。现在我的频道看起来是这样的

- #my-freelance-design-business - (Write)
  - > #crdb-industries/#design  - (Read)
  - > #zed/#design              - (Read)

问题迎刃而解了。我只对 #crdb-industries/#design 拥有读取权限,我对我的个人频道的写入权限完全独立于其子频道。太棒了!但这个解决方案有一个主要的缺点:我们不这样思考。

Zed 致力于将用户体验放在首位。我们努力消除你的意愿和计算机操作之间的障碍。人类世界目前是按照成员资格和角色来组织的。无论这是否是一件好事,人们都会以“我想让 Mikayla 成为 Zed 的成员”这样的词语来思考,我们需要让表达这一点变得简单。那么,为什么不同时使用 ACL 和 OCaps 呢?

如果我们要使用 ACL,我们就必须恢复级联成员角色。在我们这个用例中,成为一个频道的成员却不是其子频道的成员,这没有道理。但为了解决权限提升问题,我们可以在 DAG 的每个**边**上添加一种新的规则,在边创建时选择:Inherits up to _。让我们用这个新规则重新绘制我们的示例,这次在 FigJam 中,因为内容更多。

A diagram showing two root channels, #zed and #my-freelance-design-business, and two members in each channel: Nathan and Mikayla. In #zed, Nathan is an Admin, and Mikayla is a member. In #my-freelance-design-business, Nathan is a Member and Mikayla is an Admin. There is a third channel, called #design, with arrows connecting it to the other two channels. The arrow to #zed is tagged with Inherits up to Admin and the arrow to #my-freelance-design-business is tagged with Inherits up to Member
一张图表显示了两个根频道,#zed 和 #my-freelance-design-business,以及每个频道中的两名成员:Nathan 和 Mikayla。在 #zed 中,Nathan 是管理员,Mikayla 是成员。在 #my-freelance-design-business 中,Nathan 是成员,Mikayla 是管理员。还有一个名为 #design 的第三个频道,箭头将其连接到其他两个频道。指向 #zed 的箭头标记为“继承至管理员”,指向 #my-freelance-design-business 的箭头标记为“继承至成员”。

在此设置中,边的继承规则充当级联角色的过滤器,而不是权限本身(纯 OCap 解决方案)。这解决了权限提升攻击,因为我在我自己的频道上的管理员角色在沿 #my-freelance-design-business -> #design 边传递时转换为简单的成员角色。将 #zed -> #design 边标记为 Inherits up to Admin 也解决了窃取我同事管理员权限的反向问题。在 DAG 中存在一条路径,其中 Nathan 的管理员角色未被过滤掉,因此他是 #design 的管理员。他在 Mikayla 频道中的成员资格不会干扰他在 Zed 的管理员角色。

更棒的是,我们也可以用这个系统模拟**安全的**权限提升。假设我在业余时间成为了 Rust Analyzer 的核心贡献者,并且我们有一个这样的频道设置

A diagram showing two root channels, #zed and #rust-analyzer, and Mikayla's membership in each channel: In #zed Mikayla is a member. In #rust-analyzer, Mikayla is an Admin. Below #zed is another channel, #open-source, and below that channel another #zed-rust-analyzer which is tagged with Inherits up to Admin. There is also an edge between #rust-analyzer and #zed-rust-analyzer also with an Inherits up to Admin tag. Mikayla's membership role in #zed-rust-analyzer is shown to be Admin.
一张图表显示了两个根频道,#zed 和 #rust-analyzer,以及 Mikayla 在每个频道中的成员资格:在 #zed 中,Mikayla 是成员。在 #rust-analyzer 中,Mikayla 是管理员。在 #zed 下方是另一个频道 #open-source,再下方是另一个频道 #zed-rust-analyzer,它被标记为“继承至管理员”。在 #rust-analyzer 和 #zed-rust-analyzer 之间也有一条边,同样带有“继承至管理员”的标签。Mikayla 在 #zed-rust-analyzer 中的成员角色显示为管理员。

由于 Rust Analyzer 和 Zed 共同管理一个频道,并且我在 #rust-analyzer 中拥有管理员角色,这些规则允许我的角色仅在 #zed-rust-analyzer 中升级为管理员。由于成员资格是向下级联,而不是向上级联,我在所有其他 #zed 频道中的成员角色得以维持,正如它应该的那样。

现在我们可以表达我们都习惯的简单、以人为本的成员角色,而无需牺牲灵活性和表达能力,也无需打开微不足道的安全漏洞。🎉🎉🎉

希望我们下周能开始对 Channels 功能进行内部测试。我很高兴看到它在实践中如何运作!特别感谢 Fission 的 Quinn Wilton,在上次我们讨论权限问题时与我一起完成了这个综合方案。

内森

对我来说是激动人心的一周。

今天我整理了这份插件提案,并与 Julia 分享了它。一旦我们让基础功能运行起来,我很乐意做一些宏功能,使 Rust 对象作为托管对象在 V8 中暴露出来变得相当容易。

我在提高 Zed 样式设置效率方面也取得了进展。动态加载的效果不是特别好,但我最终选择了一个编译速度非常快的沙盒,将 UI 更新的反馈时间缩短到几秒钟。我们最终可以做得更好,但相比之下,这个循环已经相当可用了。下一步是允许在运行时调整我们最常见元素的样式。结合打印表示,我们有可能相当高效地将其往返于代码。所有这一切都是因为我们需要 Zed 成为设计师编写代码的地方。我认为这对每个人来说都更高效。

展望未来,我希望尝试将 Zed 的主题建立在 Rosé Pine 系列的基础上。这是他们的模式,但我们用领域特定、色相中性的名称替换了他们可爱命名的颜色

pub struct ThemeColors {
    pub base: Range<Hsla>,
    pub surface: Range<Hsla>,
    pub overlay: Range<Hsla>,
    pub muted: Range<Hsla>,
    pub subtle: Range<Hsla>,
    pub text: Range<Hsla>,
    pub highlight_low: Range<Hsla>,
    pub highlight_med: Range<Hsla>,
    pub highlight_high: Range<Hsla>,
    pub success: Range<Hsla>,
    pub warning: Range<Hsla>,
    pub error: Range<Hsla>,
    pub inserted: Range<Hsla>,
    pub deleted: Range<Hsla>,
    pub modified: Range<Hsla>,
}

这些范围是两种颜色,可以在 0.0 到 1.0 之间采样,在 HSL 颜色空间中,在给定范围的起点和终点之间进行线性插值。

我目前采样的值有点随意,但这段代码能让你体会到它的精髓。值得注意的是,组件对于一个类型 V 是通用的,该类型 V 包含跨多个帧存在的数据。

impl<V> Playground<V> {
    pub fn new() -> Self {
        Self(PhantomData)
    }
 
    pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext<V>) -> impl Element<V> {
        workspace(&rose_pine::dawn())
    }
}
 
fn workspace<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
    column()
        .size(auto())
        .fill(theme.base(0.5))
        .text_color(theme.text(0.5))
        .child(title_bar(theme))
        .child(stage(theme))
        .child(status_bar(theme))
}
 
fn title_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
    row()
        .fill(theme.base(0.2))
        .justify(0.)
        .width(auto())
        .child(text("Zed Playground"))
}
 
fn stage<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
    row().fill(theme.surface(0.9))
}
 
fn status_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
    row().fill(theme.surface(0.1))
}

我计划探索 taffy 作为布局模型,因此上述某些语法可能会更改,以更强地与它所模仿的 Web 概念保持一致。熟悉度是好事。我们只需要速度。


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

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


我们正在招聘!

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