From e792286660220c8ea7c8a743276b69ff58d67f00 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 14 Dec 2023 15:54:31 -0500 Subject: [PATCH 001/334] Add contributing guide --- CONTRIBUTING.md | 39 ++++++++++++++++++ .../screenshots/staff_usage_of_channels.png | Bin 0 -> 134356 bytes 2 files changed, 39 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 assets/screenshots/staff_usage_of_channels.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..fbb2aa29488f5b62adcd35fbe3a25c54614ea894 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# CONTRIBUTING + +## Introduction + +[Since ~February 2022, the Zed Industries team has been exclusively using Zed to build Zed](https://x.com/nathansobo/status/1497958891509932035). We've built these tools to specifically address our own issues and frustrations with the current state of collaborative coding. These are not features we've built to simply look flashy, we work in channels every week, aggressively dogfooding our own tools. + +![Staff usage of channels (metrics were not being collected before August, 2023)](./assets/screenshots/staff_usage_of_channels.png) + +While we still have improvements to make, we believe we've sanded down a lot of the sharp edges and that experience is both smooth and enjoyable - one that gets you as close to hypothetically sitting next to your teammates as possible, even if you're potentially on different sides of the globe. We want to continue working this way amongst ourselves, but we are extremely excited to work with *you* in this way. We invite you to contribute to Zed *through* Zed. + +If you're new to Zed's channels, here's a guide [link to up-to-date docs] to help bring you up to speed. + +## Contribution ideas + +*If you already have an idea of what you'd like to contribute, you can skip this section.* + +- Our public roadmap [include link] shows the largest, most-wanted features we plan to add to Zed. +- Our [Top-Ranking Issues issue](https://github.com/zed-industries/community/issues/52) shows the most popular feature requests and issues, as voted on by the community. + +*If you are a plugin developer looking to contribute by building out the Zed ecosystem, have a look at these [issues](https://github.com/zed-industries/community/issues?q=is%3Aopen+is%3Aissue+label%3A%22potential+plugin%22+sort%3Areactions-%2B1-desc).* + +## Proposal & Discussion + +Once you have an idea of what you'd like to contribute, you'll want to communicate this to the team. Find a public channel [link to list of all public channels] that is relevant to your contribution, check the channel notes to see which Zed team members typically work in that channel, and post a message in the chat. If you're not sure which channel is best, you can post in the channel. *Please wait to begin working on your contribution until you've received feedback from the team. Turning down a contribution that was not discussed beforehand is a bummer for everyone.* + +## Implementation & Help + +Once approved, feel free to begin working on your contribution. If you have any questions, you can post in the channel you originally proposed your contribution in, or you can post in the channel. If you need help, reach out to a Zed teammate - we're happy to pair with you to help you learn the codebase and get your contribution merged. + +Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming. We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed. + +--- + +Other things to mention here +- [ ] Etiquette +- [ ] CLA + +Things to do: +- [ ] Put names devs who "own" each channel in the channel notes diff --git a/assets/screenshots/staff_usage_of_channels.png b/assets/screenshots/staff_usage_of_channels.png new file mode 100644 index 0000000000000000000000000000000000000000..b9e607e59cfdd31c9477756e1f40283dde9ccbe0 GIT binary patch literal 134356 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

yaSU}o+QpZDB* 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

QPF7&Ie;E`5;HK&Xr^?h}Mr^uf59UYPynwt9# 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 literal 0 HcmV?d00001 From 5f25fa1227de9bb909a0681eb719fe07f8c098cc Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 14 Dec 2023 15:58:30 -0500 Subject: [PATCH 002/334] Tweak wording --- CONTRIBUTING.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fbb2aa29488f5b62adcd35fbe3a25c54614ea894..110120c061603739c5f7bd9d7fdf783346fcef5a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,19 +2,21 @@ ## Introduction -[Since ~February 2022, the Zed Industries team has been exclusively using Zed to build Zed](https://x.com/nathansobo/status/1497958891509932035). We've built these tools to specifically address our own issues and frustrations with the current state of collaborative coding. These are not features we've built to simply look flashy, we work in channels every week, aggressively dogfooding our own tools. +[Since ~February 2022, the Zed Industries team has been exclusively using Zed to build Zed](https://x.com/nathansobo/status/1497958891509932035). We've built these tools to specifically address our own issues and frustrations with the current state of collaborative coding. These are not features we've built to simply look flashy, we work in channels every day of the workweek, aggressively dogfooding everything. -![Staff usage of channels (metrics were not being collected before August, 2023)](./assets/screenshots/staff_usage_of_channels.png) +![Staff usage of channels](./assets/screenshots/staff_usage_of_channels.png) -While we still have improvements to make, we believe we've sanded down a lot of the sharp edges and that experience is both smooth and enjoyable - one that gets you as close to hypothetically sitting next to your teammates as possible, even if you're potentially on different sides of the globe. We want to continue working this way amongst ourselves, but we are extremely excited to work with *you* in this way. We invite you to contribute to Zed *through* Zed. +*Metrics were not being collected before August 2023* + +While we still have improvements to make, we believe we've sanded down a lot of the sharp edges and that the experience is both smooth and enjoyable - one that gets you as close to hypothetically sitting next to your teammates as possible, even if you're potentially on different sides of the globe. We want to continue working this way amongst ourselves and we are extremely excited to work with *you* in this way. We invite you to contribute to Zed *through* Zed. If you're new to Zed's channels, here's a guide [link to up-to-date docs] to help bring you up to speed. ## Contribution ideas -*If you already have an idea of what you'd like to contribute, you can skip this section.* +If you already have an idea of what you'd like to contribute, you can skip this section, otherwise, here are a few resources to help you find something to work on: -- Our public roadmap [include link] shows the largest, most-wanted features we plan to add to Zed. +- Our public roadmap [include link] details what features we plan to add to Zed. - Our [Top-Ranking Issues issue](https://github.com/zed-industries/community/issues/52) shows the most popular feature requests and issues, as voted on by the community. *If you are a plugin developer looking to contribute by building out the Zed ecosystem, have a look at these [issues](https://github.com/zed-industries/community/issues?q=is%3Aopen+is%3Aissue+label%3A%22potential+plugin%22+sort%3Areactions-%2B1-desc).* From c7d60bb003fa8ff1dc829778ba9b88ad408db122 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 14 Dec 2023 16:10:53 -0500 Subject: [PATCH 003/334] Add some TODOs --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 110120c061603739c5f7bd9d7fdf783346fcef5a..42afe02fd2663f72c95d8d601f51cab6e046c367 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,9 @@ Reviewing code in a pull request, after the fact, is hard and tedious - the team Other things to mention here - [ ] Etiquette - [ ] CLA +- [ ] Importance of tests +- [ ] Look over Piotr's PR and pull in what this is missing (tour of the codebase, etc.) + - https://github.com/zed-industries/zed/pull/3143/files Things to do: - [ ] Put names devs who "own" each channel in the channel notes From 2fd9ac506fa24cf6664d14009142047847b5b70b Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 15 Dec 2023 01:47:59 -0500 Subject: [PATCH 004/334] Tweak wording --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 42afe02fd2663f72c95d8d601f51cab6e046c367..09e9f3d1d16e1657485a4ed5aa6b0e3da0aeb55b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ ![Staff usage of channels](./assets/screenshots/staff_usage_of_channels.png) -*Metrics were not being collected before August 2023* +*Channel metrics were not collected prior to August 2023* While we still have improvements to make, we believe we've sanded down a lot of the sharp edges and that the experience is both smooth and enjoyable - one that gets you as close to hypothetically sitting next to your teammates as possible, even if you're potentially on different sides of the globe. We want to continue working this way amongst ourselves and we are extremely excited to work with *you* in this way. We invite you to contribute to Zed *through* Zed. From 6cb1a08cc9fe8d969e6ca0b2a5f44f3934b43f51 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 15 Dec 2023 17:18:52 -0500 Subject: [PATCH 005/334] Add notes --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09e9f3d1d16e1657485a4ed5aa6b0e3da0aeb55b..64fb8246517d3ac3f64363263497bc5c95726117 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,8 @@ Other things to mention here - [ ] Importance of tests - [ ] Look over Piotr's PR and pull in what this is missing (tour of the codebase, etc.) - https://github.com/zed-industries/zed/pull/3143/files +- [ ] Ask people to check the PRs to see if something has already been started on +- [ ] Maybe have a channel that maps out what teammates / community users are working on what, so people can see what's being worked on Things to do: - [ ] Put names devs who "own" each channel in the channel notes From 3c2db6a4db646e53f20df2b648bf49e8819bcfc4 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 18 Dec 2023 13:35:07 -0500 Subject: [PATCH 006/334] Add some rough ideas Co-Authored-By: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- CONTRIBUTING.md | 87 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 64fb8246517d3ac3f64363263497bc5c95726117..bde030de4746c42c8d4667e9729c455f7127dbc0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,59 @@ # CONTRIBUTING -## Introduction +Thanks for your interest in contributing to Zed, the collaborative platform that is also a code editor! + +## Contribution ideas + +If you already have an idea of what you'd like to contribute, you can skip this section, otherwise, here are a few resources to help you find something to work on: + +- Our public roadmap [include link] details what features we plan to add to Zed. +- Our [Top-Ranking Issues issue](https://github.com/zed-industries/community/issues/52) shows the most popular feature requests and issues, as voted on by the community. + +*If you are a plugin developer looking to contribute by building out the Zed ecosystem, have a look at these [issues](https://github.com/zed-industries/community/issues?q=is%3Aopen+is%3Aissue+label%3A%22potential+plugin%22+sort%3Areactions-%2B1-desc).* + +In the short term, we want to provide a generalized solutions to these problems (plugin system/theme system), so we are not looking to add these features to Zed itself. + +- Adding languages +- Themes + +## Resources + +### Bird-eye's view of Zed + +Zed is made up of several smaller crates - let's go over those you're most likely to interact with: +- [gpui](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. +- [editor](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions. +- [project](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP. +- [workspace](/crates/workspace) handles local state serialization and groups projects together. +- [vim](/crates/vim) is a thin implementation of Vim workflow over `editor`. +- [lsp](/crates/lsp) handles communication with external LSP server. +- [language](/crates/language) drives `editor`'s understanding of language - from providing a list of symbols to the syntax map. +- [collab](/crates/collab) is the collaboration server itself, driving the collaboration features such as project sharing. +- [rpc](/crates/rpc) defines messages to be exchanged with collaboration server. + +// Let's try to make whoever we come into contact with for the first time +// well-equiped to discuss basic concepts around Zed +// Ideally these should link to mdbook/source code docs (doubtful given how source code docs might be more in-depth than necessary) +### Important concepts + +- Views vs Models +- Contexts +- Action +- UI + - Render vs RenderOnce + - ui crate + - storybook +- Workspace +- Project + - Worktree +- vim crate + - Editor + - Multibuffers +- Settings + +## Zed channels + +Once you have an idea of what you'd like to contribute, you'll want to communicate this to the team. If you're new to Zed's channels, here's a guide [link to up-to-date docs] to help bring you up to speed. [Since ~February 2022, the Zed Industries team has been exclusively using Zed to build Zed](https://x.com/nathansobo/status/1497958891509932035). We've built these tools to specifically address our own issues and frustrations with the current state of collaborative coding. These are not features we've built to simply look flashy, we work in channels every day of the workweek, aggressively dogfooding everything. @@ -10,37 +63,43 @@ While we still have improvements to make, we believe we've sanded down a lot of the sharp edges and that the experience is both smooth and enjoyable - one that gets you as close to hypothetically sitting next to your teammates as possible, even if you're potentially on different sides of the globe. We want to continue working this way amongst ourselves and we are extremely excited to work with *you* in this way. We invite you to contribute to Zed *through* Zed. -If you're new to Zed's channels, here's a guide [link to up-to-date docs] to help bring you up to speed. +### Proposal & Discussion -## Contribution ideas +To do that, find a public channel [link to list of all public channels] that is relevant to your contribution, check the channel notes to see which Zed team members typically work in that channel, and post a message in the chat. If you're not sure which channel is best, you can post in the channel. *Please wait to begin working on your contribution until you've received feedback from the team. Turning down a contribution that was not discussed beforehand is a bummer for everyone.* -If you already have an idea of what you'd like to contribute, you can skip this section, otherwise, here are a few resources to help you find something to work on: +## Implementation & Help -- Our public roadmap [include link] details what features we plan to add to Zed. -- Our [Top-Ranking Issues issue](https://github.com/zed-industries/community/issues/52) shows the most popular feature requests and issues, as voted on by the community. +Once approved, feel free to begin working on your contribution. If you have any questions, you can post in the channel you originally proposed your contribution in, or you can post in the channel. If you need help, reach out to a Zed teammate - we're happy to pair with you to help you learn the codebase and get your contribution merged. -*If you are a plugin developer looking to contribute by building out the Zed ecosystem, have a look at these [issues](https://github.com/zed-industries/community/issues?q=is%3Aopen+is%3Aissue+label%3A%22potential+plugin%22+sort%3Areactions-%2B1-desc).* +Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming. We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed. -## Proposal & Discussion +--- Piotr's original contribution guide --- -Once you have an idea of what you'd like to contribute, you'll want to communicate this to the team. Find a public channel [link to list of all public channels] that is relevant to your contribution, check the channel notes to see which Zed team members typically work in that channel, and post a message in the chat. If you're not sure which channel is best, you can post in the channel. *Please wait to begin working on your contribution until you've received feedback from the team. Turning down a contribution that was not discussed beforehand is a bummer for everyone.* +Read on if you're looking for an outline of your first contribution - from finding your way around the codebase and asking questions, through modifying and testing the changes, finishing off with submitting your changes for review and interacting with Zed core team and Zed community as a whole. -## Implementation & Help +### Getting in touch +We believe that journeys are best when shared - hence there are multiple outlets for Zed users and developers to share their success stories and hurdles. -Once approved, feel free to begin working on your contribution. If you have any questions, you can post in the channel you originally proposed your contribution in, or you can post in the channel. If you need help, reach out to a Zed teammate - we're happy to pair with you to help you learn the codebase and get your contribution merged. +If you have questions, ask them away on our [Discord](https://discord.gg/XTtXmZYEpN) or in a dedicated [Zed channel](https://zed.dev/preview/channel/open-source-81). We also plan to organise office hours on a weekly basis - they will take place in forelinked Zed channel. -Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming. We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed. +All activity in Zed communities is subject to our [Code of Conduct](https://docs.zed.dev/community/code-of-conduct). ---- +If you're just starting out with Zed, it might be worthwhile to look at some of the other crates that implement bits of UI - such as [go to line](/crates/go_to_line) modal that's bound to ctrl-g by default in Zed. + +### Upstreaming your changes +Here be dragons :) +--- Other things to mention here - [ ] Etiquette - [ ] CLA - [ ] Importance of tests - [ ] Look over Piotr's PR and pull in what this is missing (tour of the codebase, etc.) - - https://github.com/zed-industries/zed/pull/3143/files + - See above - [ ] Ask people to check the PRs to see if something has already been started on - [ ] Maybe have a channel that maps out what teammates / community users are working on what, so people can see what's being worked on +- [ ] Mention Discord or keep it only focused on Zed channels? +- [ ] Mention issue triage doc (https://github.com/zed-industries/community/blob/main/processes/issues_triage.md)? Things to do: - [ ] Put names devs who "own" each channel in the channel notes From 5904bcf1c20638d63b244a1b2b038ec9a664ba1c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 Jan 2024 13:51:38 +0100 Subject: [PATCH 007/334] Use taffy to retrieve the parent for a given layout node --- Cargo.lock | 7 +++--- crates/gpui/Cargo.toml | 2 +- crates/gpui/src/taffy.rs | 46 ++++++++++++++++------------------------ 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 54e2f483d8a655f3d5e5c6e200b918275f968a34..ca55567cbb2a78661cb3c91b3e31c89b281bb9b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3091,9 +3091,9 @@ dependencies = [ [[package]] name = "grid" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df00eed8d1f0db937f6be10e46e8072b0671accb504cf0f959c5c52c679f5b9" +checksum = "d196ffc1627db18a531359249b2bf8416178d84b729f3cebeb278f285fb9b58c" [[package]] name = "h2" @@ -7665,11 +7665,12 @@ dependencies = [ [[package]] name = "taffy" version = "0.3.11" -source = "git+https://github.com/DioxusLabs/taffy?rev=1876f72bee5e376023eaa518aa7b8a34c769bd1b#1876f72bee5e376023eaa518aa7b8a34c769bd1b" +source = "git+https://github.com/zed-industries/taffy?rev=5e6c2d23e70e9f2156911d11050cb686362ba277#5e6c2d23e70e9f2156911d11050cb686362ba277" dependencies = [ "arrayvec 0.7.4", "grid", "num-traits", + "serde", "slotmap", ] diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 6ea3524fcc4eb2a60ae2411f7eed6a2ef05a17a8..118753aafea5981e0dd2119f9b4203c70b85695d 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -46,7 +46,7 @@ serde_derive.workspace = true serde_json.workspace = true smallvec.workspace = true smol.workspace = true -taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" } +taffy = { git = "https://github.com/zed-industries/taffy", rev = "5e6c2d23e70e9f2156911d11050cb686362ba277" } thiserror.workspace = true time.workspace = true tiny-skia = "0.5" diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 0ebd394217ae06c6a2256281f389ab506bfca934..a77127e80176f6f74ca5f789a2570d0a630f9a33 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -6,15 +6,12 @@ use collections::{FxHashMap, FxHashSet}; use smallvec::SmallVec; use std::fmt::Debug; use taffy::{ - geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize}, - style::AvailableSpace as TaffyAvailableSpace, - tree::NodeId, - Taffy, + AvailableSpace as TaffyAvailableSpace, NodeId, Point as TaffyPoint, Rect as TaffyRect, + Size as TaffySize, TaffyTree, TraversePartialTree, }; pub struct TaffyLayoutEngine { - taffy: Taffy, - children_to_parents: FxHashMap, + tree: TaffyTree, absolute_layout_bounds: FxHashMap>, computed_layouts: FxHashSet, nodes_to_measure: FxHashMap< @@ -34,8 +31,7 @@ static EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by constructi impl TaffyLayoutEngine { pub fn new() -> Self { TaffyLayoutEngine { - taffy: Taffy::new(), - children_to_parents: FxHashMap::default(), + tree: TaffyTree::new(), absolute_layout_bounds: FxHashMap::default(), computed_layouts: FxHashSet::default(), nodes_to_measure: FxHashMap::default(), @@ -43,8 +39,7 @@ impl TaffyLayoutEngine { } pub fn clear(&mut self) { - self.taffy.clear(); - self.children_to_parents.clear(); + self.tree.clear(); self.absolute_layout_bounds.clear(); self.computed_layouts.clear(); self.nodes_to_measure.clear(); @@ -58,18 +53,13 @@ impl TaffyLayoutEngine { ) -> LayoutId { let style = style.to_taffy(rem_size); if children.is_empty() { - self.taffy.new_leaf(style).expect(EXPECT_MESSAGE).into() + self.tree.new_leaf(style).expect(EXPECT_MESSAGE).into() } else { - let parent_id = self - .taffy + self.tree // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId. .new_with_children(style, unsafe { std::mem::transmute(children) }) .expect(EXPECT_MESSAGE) - .into(); - for child_id in children { - self.children_to_parents.insert(*child_id, parent_id); - } - parent_id + .into() } } @@ -83,7 +73,7 @@ impl TaffyLayoutEngine { let style = style.to_taffy(rem_size); let layout_id = self - .taffy + .tree .new_leaf_with_context(style, ()) .expect(EXPECT_MESSAGE) .into(); @@ -96,7 +86,7 @@ impl TaffyLayoutEngine { fn count_all_children(&self, parent: LayoutId) -> anyhow::Result { let mut count = 0; - for child in self.taffy.children(parent.0)? { + for child in self.tree.children(parent.0)? { // Count this child. count += 1; @@ -112,12 +102,12 @@ impl TaffyLayoutEngine { fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result { println!( "{parent:?} at depth {depth} has {} children", - self.taffy.child_count(parent.0)? + self.tree.child_count(parent.0) ); let mut max_child_depth = 0; - for child in self.taffy.children(parent.0)? { + for child in self.tree.children(parent.0)? { max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?); } @@ -129,7 +119,7 @@ impl TaffyLayoutEngine { fn get_edges(&self, parent: LayoutId) -> anyhow::Result> { let mut edges = Vec::new(); - for child in self.taffy.children(parent.0)? { + for child in self.tree.children(parent.0)? { edges.push((parent, LayoutId(child))); edges.extend(self.get_edges(LayoutId(child))?); @@ -162,7 +152,7 @@ impl TaffyLayoutEngine { while let Some(id) = stack.pop() { self.absolute_layout_bounds.remove(&id); stack.extend( - self.taffy + self.tree .children(id.into()) .expect(EXPECT_MESSAGE) .into_iter() @@ -172,7 +162,7 @@ impl TaffyLayoutEngine { } // let started_at = std::time::Instant::now(); - self.taffy + self.tree .compute_layout_with_measure( id.into(), available_space.into(), @@ -199,14 +189,14 @@ impl TaffyLayoutEngine { return layout; } - let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE); + let layout = self.tree.layout(id.into()).expect(EXPECT_MESSAGE); let mut bounds = Bounds { origin: layout.location.into(), size: layout.size.into(), }; - if let Some(parent_id) = self.children_to_parents.get(&id).copied() { - let parent_bounds = self.layout_bounds(parent_id); + if let Some(parent_id) = self.tree.parent(id.0) { + let parent_bounds = self.layout_bounds(parent_id.into()); bounds.origin += parent_bounds.origin; } self.absolute_layout_bounds.insert(id, bounds); From 84c36066bcc20e3ef854d1527f01595e1e93fdf7 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 Jan 2024 19:07:20 +0100 Subject: [PATCH 008/334] Start on caching views Co-Authored-By: Nathan Sobo --- crates/gpui/src/geometry.rs | 6 +- crates/gpui/src/style.rs | 2 +- crates/gpui/src/taffy.rs | 28 +++++-- crates/gpui/src/view.rs | 122 +++++++++++++++++++---------- crates/gpui/src/window.rs | 9 +++ crates/workspace/src/dock.rs | 2 +- crates/workspace/src/pane_group.rs | 6 +- 7 files changed, 117 insertions(+), 58 deletions(-) diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index a50de8c344247c705d24c8d9a0e94c7c799278bb..89e47994a34b76d4310e3d8afe0c15be411608be 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -2272,7 +2272,7 @@ impl From for GlobalPixels { /// For example, if the root element's font-size is `16px`, then `1rem` equals `16px`. A length of `2rems` would then be `32px`. /// /// [set_rem_size]: crate::WindowContext::set_rem_size -#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg)] +#[derive(Clone, Copy, Default, Add, Sub, Mul, Div, Neg, PartialEq)] pub struct Rems(pub f32); impl Mul for Rems { @@ -2295,7 +2295,7 @@ impl Debug for Rems { /// affected by the current font size, or a number of rems, which is relative to the font size of /// the root element. It is used for specifying dimensions that are either independent of or /// related to the typographic scale. -#[derive(Clone, Copy, Debug, Neg)] +#[derive(Clone, Copy, Debug, Neg, PartialEq)] pub enum AbsoluteLength { /// A length in pixels. Pixels(Pixels), @@ -2366,7 +2366,7 @@ impl Default for AbsoluteLength { /// This enum represents lengths that have a specific value, as opposed to lengths that are automatically /// determined by the context. It includes absolute lengths in pixels or rems, and relative lengths as a /// fraction of the parent's size. -#[derive(Clone, Copy, Neg)] +#[derive(Clone, Copy, Neg, PartialEq)] pub enum DefiniteLength { /// An absolute length specified in pixels or rems. Absolute(AbsoluteLength), diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 244ccebf2498fb9ff275d0818215a9ba658ffc02..67b9b855d26ac159566daf748c4a8136b83f5cc0 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -146,7 +146,7 @@ pub enum WhiteSpace { Nowrap, } -#[derive(Refineable, Clone, Debug)] +#[derive(Refineable, Clone, Debug, PartialEq)] #[refineable(Debug)] pub struct TextStyle { pub color: Hsla, diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index a77127e80176f6f74ca5f789a2570d0a630f9a33..430d928cbf5bdc55f27928a9d5c65bb6aa2645d7 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -12,6 +12,7 @@ use taffy::{ pub struct TaffyLayoutEngine { tree: TaffyTree, + styles: FxHashMap, absolute_layout_bounds: FxHashMap>, computed_layouts: FxHashSet, nodes_to_measure: FxHashMap< @@ -32,6 +33,7 @@ impl TaffyLayoutEngine { pub fn new() -> Self { TaffyLayoutEngine { tree: TaffyTree::new(), + styles: FxHashMap::default(), absolute_layout_bounds: FxHashMap::default(), computed_layouts: FxHashSet::default(), nodes_to_measure: FxHashMap::default(), @@ -43,6 +45,11 @@ impl TaffyLayoutEngine { self.absolute_layout_bounds.clear(); self.computed_layouts.clear(); self.nodes_to_measure.clear(); + self.styles.clear(); + } + + pub fn requested_style(&self, layout_id: LayoutId) -> Option<&Style> { + self.styles.get(&layout_id) } pub fn request_layout( @@ -51,16 +58,21 @@ impl TaffyLayoutEngine { rem_size: Pixels, children: &[LayoutId], ) -> LayoutId { - let style = style.to_taffy(rem_size); - if children.is_empty() { - self.tree.new_leaf(style).expect(EXPECT_MESSAGE).into() + let taffy_style = style.to_taffy(rem_size); + let layout_id = if children.is_empty() { + self.tree + .new_leaf(taffy_style) + .expect(EXPECT_MESSAGE) + .into() } else { self.tree // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId. - .new_with_children(style, unsafe { std::mem::transmute(children) }) + .new_with_children(taffy_style, unsafe { std::mem::transmute(children) }) .expect(EXPECT_MESSAGE) .into() - } + }; + self.styles.insert(layout_id, style.clone()); + layout_id } pub fn request_measured_layout( @@ -70,14 +82,16 @@ impl TaffyLayoutEngine { measure: impl FnMut(Size>, Size, &mut WindowContext) -> Size + 'static, ) -> LayoutId { - let style = style.to_taffy(rem_size); + let style = style.clone(); + let taffy_style = style.to_taffy(rem_size); let layout_id = self .tree - .new_leaf_with_context(style, ()) + .new_leaf_with_context(taffy_style, ()) .expect(EXPECT_MESSAGE) .into(); self.nodes_to_measure.insert(layout_id, Box::new(measure)); + self.styles.insert(layout_id, style.clone()); layout_id } diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 4472da02e71fda1bb17d4353056b67ad58639813..7d9b8092ee10531f6c80d1d874016bb8ebd00108 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -1,8 +1,8 @@ use crate::{ seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow, - Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement, - LayoutId, Model, Pixels, Point, Render, Size, ViewContext, VisualContext, WeakModel, - WindowContext, + Bounds, ContentMask, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, + IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style, TextStyle, + ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; use std::{ @@ -17,6 +17,19 @@ pub struct View { impl Sealed for View {} +pub struct AnyViewState { + root_style: Style, + cache_key: Option, + element: Option, +} + +struct ViewCacheKey { + bounds: Bounds, + stacking_order: StackingOrder, + content_mask: ContentMask, + text_style: TextStyle, +} + impl Entity for View { type Weak = WeakView; @@ -60,16 +73,6 @@ impl View { self.model.read(cx) } - // pub fn render_with(&self, component: E) -> RenderViewWith - // where - // E: 'static + Element, - // { - // RenderViewWith { - // view: self.clone(), - // element: Some(component), - // } - // } - pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle where V: FocusableView, @@ -183,16 +186,20 @@ impl Eq for WeakView {} #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, - layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), - paint: fn(&AnyView, &mut AnyElement, &mut WindowContext), + request_layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), + cache: bool, } impl AnyView { + pub fn cached(mut self) -> Self { + self.cache = true; + self + } + pub fn downgrade(&self) -> AnyWeakView { AnyWeakView { model: self.model.downgrade(), - layout: self.layout, - paint: self.paint, + layout: self.request_layout, } } @@ -201,8 +208,8 @@ impl AnyView { Ok(model) => Ok(View { model }), Err(model) => Err(Self { model, - layout: self.layout, - paint: self.paint, + request_layout: self.request_layout, + cache: self.cache, }), } } @@ -222,9 +229,9 @@ impl AnyView { cx: &mut WindowContext, ) { cx.with_absolute_element_offset(origin, |cx| { - let (layout_id, mut rendered_element) = (self.layout)(self, cx); + let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); cx.compute_layout(layout_id, available_space); - (self.paint)(self, &mut rendered_element, cx); + rendered_element.paint(cx); }) } } @@ -233,30 +240,65 @@ impl From> for AnyView { fn from(value: View) -> Self { AnyView { model: value.model.into_any(), - layout: any_view::layout::, - paint: any_view::paint, + request_layout: any_view::request_layout::, + cache: false, } } } impl Element for AnyView { - type State = Option; + type State = AnyViewState; fn request_layout( &mut self, - _state: Option, + state: Option, cx: &mut WindowContext, ) -> (LayoutId, Self::State) { - let (layout_id, state) = (self.layout)(self, cx); - (layout_id, Some(state)) + if self.cache { + if let Some(state) = state { + let layout_id = cx.request_layout(&state.root_style, None); + return (layout_id, state); + } + } + + let (layout_id, element) = (self.request_layout)(self, cx); + let root_style = cx.layout_style(layout_id).unwrap().clone(); + let state = AnyViewState { + root_style, + cache_key: None, + element: Some(element), + }; + (layout_id, state) } - fn paint(&mut self, _: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - debug_assert!( - state.is_some(), - "state is None. Did you include an AnyView twice in the tree?" - ); - (self.paint)(self, state.as_mut().unwrap(), cx) + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + if !self.cache { + state.element.take().unwrap().paint(cx); + return; + } + + if let Some(cache_key) = state.cache_key.as_mut() { + if cache_key.bounds == bounds + && cache_key.content_mask == cx.content_mask() + && cache_key.stacking_order == *cx.stacking_order() + && cache_key.text_style == cx.text_style() + { + println!("could reuse geometry for view {}", self.entity_id()); + } + } + + let mut element = state + .element + .take() + .unwrap_or_else(|| (self.request_layout)(self, cx).1); + element.draw(bounds.origin, bounds.size.into(), cx); + + state.cache_key = Some(ViewCacheKey { + bounds, + stacking_order: cx.stacking_order().clone(), + content_mask: cx.content_mask(), + text_style: cx.text_style(), + }); } } @@ -287,7 +329,6 @@ impl IntoElement for AnyView { pub struct AnyWeakView { model: AnyWeakModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), - paint: fn(&AnyView, &mut AnyElement, &mut WindowContext), } impl AnyWeakView { @@ -295,8 +336,8 @@ impl AnyWeakView { let model = self.model.upgrade()?; Some(AnyView { model, - layout: self.layout, - paint: self.paint, + request_layout: self.layout, + cache: false, }) } } @@ -305,8 +346,7 @@ impl From> for AnyWeakView { fn from(view: WeakView) -> Self { Self { model: view.model.into(), - layout: any_view::layout::, - paint: any_view::paint, + layout: any_view::request_layout::, } } } @@ -328,7 +368,7 @@ impl std::fmt::Debug for AnyWeakView { mod any_view { use crate::{AnyElement, AnyView, IntoElement, LayoutId, Render, WindowContext}; - pub(crate) fn layout( + pub(crate) fn request_layout( view: &AnyView, cx: &mut WindowContext, ) -> (LayoutId, AnyElement) { @@ -337,8 +377,4 @@ mod any_view { let layout_id = element.request_layout(cx); (layout_id, element) } - - pub(crate) fn paint(_view: &AnyView, element: &mut AnyElement, cx: &mut WindowContext) { - element.paint(cx); - } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7e4c5f93f95e6ea770d404a63a3e6795d9a4be7d..edd98e8385a324ca91b2a8d04b8ab7107096ecf8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -754,6 +754,14 @@ impl<'a> WindowContext<'a> { .request_measured_layout(style, rem_size, measure) } + pub fn layout_style(&self, layout_id: LayoutId) -> Option<&Style> { + self.window + .layout_engine + .as_ref() + .unwrap() + .requested_style(layout_id) + } + pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { let mut layout_engine = self.window.layout_engine.take().unwrap(); layout_engine.compute_layout(layout_id, available_space, self); @@ -1313,6 +1321,7 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { + println!("====================="); self.window.dirty = false; self.window.drawing = true; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c13a00b11c897b46ef5e2ac69ae10848c573ebf2..8a16ce5ab6ce7ebf026cbef03f6dbaf883f0f18d 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -601,7 +601,7 @@ impl Render for Dock { Axis::Horizontal => this.min_w(size).h_full(), Axis::Vertical => this.min_h(size).w_full(), }) - .child(entry.panel.to_any()), + .child(entry.panel.to_any().cached()), ) .child(handle) } else { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index a7368f61360ce6639dffa26ffd31f2114cd2216b..236daf60f8a747f4d43f4bc0a73109cd16e2ad3e 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -3,8 +3,8 @@ use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use collections::HashMap; use gpui::{ - point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View, - ViewContext, + point, size, AnyView, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, + Point, View, ViewContext, }; use parking_lot::Mutex; use project::Project; @@ -244,7 +244,7 @@ impl Member { .relative() .flex_1() .size_full() - .child(pane.clone()) + .child(AnyView::from(pane.clone()).cached()) .when_some(leader_border, |this, color| { this.child( div() From 364e33df82472fbb4678b70afde705495db61b4d Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 8 Jan 2024 13:25:52 -0500 Subject: [PATCH 009/334] Add period --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bde030de4746c42c8d4667e9729c455f7127dbc0..41261250f32005ef669c8b3e4ba830f4fa96a19c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ If you already have an idea of what you'd like to contribute, you can skip this *If you are a plugin developer looking to contribute by building out the Zed ecosystem, have a look at these [issues](https://github.com/zed-industries/community/issues?q=is%3Aopen+is%3Aissue+label%3A%22potential+plugin%22+sort%3Areactions-%2B1-desc).* -In the short term, we want to provide a generalized solutions to these problems (plugin system/theme system), so we are not looking to add these features to Zed itself. +In the short term, we want to provide a generalized solutions to these problems (plugin system/theme system), so we are not looking to add these features to Zed itself - Adding languages - Themes From c9193b586b7a53ddd29308bcc19f660b7b8e05a9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 8 Jan 2024 19:31:50 +0100 Subject: [PATCH 010/334] WIP --- crates/gpui/src/view.rs | 53 +++++++++++++++++++++------------------ crates/gpui/src/window.rs | 33 +++++++++++++++++++++++- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 7d9b8092ee10531f6c80d1d874016bb8ebd00108..9d504627ab185212df7df14399dfc54bcf1d6b46 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -95,7 +95,7 @@ impl Element for View { } fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { - element.take().unwrap().paint(cx); + cx.with_view_id(self.entity_id(), |cx| element.take().unwrap().paint(cx)); } } @@ -272,33 +272,36 @@ impl Element for AnyView { } fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - if !self.cache { - state.element.take().unwrap().paint(cx); - return; - } + cx.with_view_id(self.entity_id(), |cx| { + if !self.cache { + state.element.take().unwrap().paint(cx); + return; + } - if let Some(cache_key) = state.cache_key.as_mut() { - if cache_key.bounds == bounds - && cache_key.content_mask == cx.content_mask() - && cache_key.stacking_order == *cx.stacking_order() - && cache_key.text_style == cx.text_style() - { - println!("could reuse geometry for view {}", self.entity_id()); + if let Some(cache_key) = state.cache_key.as_mut() { + if cache_key.bounds == bounds + && cache_key.content_mask == cx.content_mask() + && cache_key.stacking_order == *cx.stacking_order() + && cache_key.text_style == cx.text_style() + && !cx.window.dirty_views.contains(&self.entity_id()) + { + println!("could reuse geometry for view {}", self.entity_id()); + } } - } - let mut element = state - .element - .take() - .unwrap_or_else(|| (self.request_layout)(self, cx).1); - element.draw(bounds.origin, bounds.size.into(), cx); - - state.cache_key = Some(ViewCacheKey { - bounds, - stacking_order: cx.stacking_order().clone(), - content_mask: cx.content_mask(), - text_style: cx.text_style(), - }); + let mut element = state + .element + .take() + .unwrap_or_else(|| (self.request_layout)(self, cx).1); + element.draw(bounds.origin, bounds.size.into(), cx); + + state.cache_key = Some(ViewCacheKey { + bounds, + stacking_order: cx.stacking_order().clone(), + content_mask: cx.content_mask(), + text_style: cx.text_style(), + }); + }) } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index edd98e8385a324ca91b2a8d04b8ab7107096ecf8..98c81d3883122066edef4254d90b8adb6ba0ad3c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -12,7 +12,7 @@ use crate::{ VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; -use collections::FxHashMap; +use collections::{FxHashMap, FxHashSet}; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, @@ -256,6 +256,7 @@ pub struct Window { pub(crate) element_id_stack: GlobalElementId, pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, + pub(crate) dirty_views: FxHashSet, frame_arena: Arena, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, @@ -295,6 +296,8 @@ pub(crate) struct Frame { pub(crate) next_stacking_order_id: u32, content_mask_stack: Vec>, element_offset_stack: Vec>, + pub(crate) view_parents: FxHashMap, + pub(crate) view_stack: Vec, } impl Frame { @@ -310,6 +313,8 @@ impl Frame { depth_map: Default::default(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), + view_parents: FxHashMap::default(), + view_stack: Vec::new(), } } @@ -319,6 +324,8 @@ impl Frame { self.dispatch_tree.clear(); self.depth_map.clear(); self.next_stacking_order_id = 0; + self.view_parents.clear(); + debug_assert!(self.view_stack.is_empty()); } fn focus_path(&self) -> SmallVec<[FocusId; 8]> { @@ -404,6 +411,7 @@ impl Window { element_id_stack: GlobalElementId::default(), rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), + dirty_views: FxHashSet::default(), frame_arena: Arena::new(1024 * 1024), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), @@ -1423,6 +1431,7 @@ impl<'a> WindowContext<'a> { } self.window.drawing = false; + self.window.dirty_views.clear(); ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); scene @@ -2119,6 +2128,13 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { result } + fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { + self.window_mut().next_frame.view_stack.push(view_id); + let result = f(self); + self.window_mut().next_frame.view_stack.pop(); + result + } + /// Update the global element offset relative to the current offset. This is used to implement /// scrolling. fn with_element_offset( @@ -2476,6 +2492,21 @@ impl<'a, V: 'static> ViewContext<'a, V> { } pub fn notify(&mut self) { + let mut dirty_view_id = Some(self.view.entity_id()); + while let Some(view_id) = dirty_view_id { + if self.window_cx.window.dirty_views.insert(view_id) { + dirty_view_id = self + .window_cx + .window + .rendered_frame + .view_parents + .get(&view_id) + .copied(); + } else { + break; + } + } + if !self.window.drawing { self.window_cx.notify(); self.window_cx.app.push_effect(Effect::Notify { From 0684369734e7c7fddb12ab3af0c12d400aadc795 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 17:30:24 -0800 Subject: [PATCH 011/334] Fix off by 1 error when computing available key bindings --- crates/gpui/src/key_dispatch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 22c4dffc03a78df8fde5530a3059887e91a2b876..81f66746c536e73a716b5c5559f4385fcbe5d70b 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,8 +192,8 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 1..context_stack.len() { - let context = &context_stack[0..i]; + for i in 0..context_stack.len() { + let context = &context_stack[0..=i]; if keymap.binding_enabled(binding, context) { return true; } From c40a7f344558580e48156f2c6a107a7491fc7b15 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 20:51:14 -0500 Subject: [PATCH 012/334] Stop propagation when deploying the context menu for a project panel entry (#3965) This PR fixes an issue where right-click on any project panel entry would cause the context menu on the root of the project panel (introduced in #3954) to deploy. We need to stop propagation in the handler on the inner project panel list items so that the click event doesn't bubble up the tree. Release Notes: - Fixed an issue where the project panel was always deploying the root context menu rather than on the clicked item. --- crates/project_panel/src/project_panel.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index ee016f399e0b2979423f5d5dc625d616013526d3..727ab7e859d237c8729401e6dcee90f8529616c8 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1433,6 +1433,9 @@ impl ProjectPanel { })) .on_secondary_mouse_down(cx.listener( move |this, event: &MouseDownEvent, cx| { + // Stop propagation to prevent the catch-all context menu for the project + // panel from being deployed. + cx.stop_propagation(); this.deploy_context_menu(event.position, entry_id, cx); }, )), From 27d4d727c3c0392feb83c1d0aeff4ffaff4e1448 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 8 Jan 2024 18:21:54 -0800 Subject: [PATCH 013/334] Attempt to write test --- crates/gpui/src/action.rs | 22 +++++++-- crates/gpui/src/key_dispatch.rs | 88 ++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index e335c4255e4deb0d6c5720b03ce017952a6fa229..b86dafe62cb6e41bdaa5efc357abdb3f55f83ae1 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -114,14 +114,26 @@ impl ActionRegistry { pub(crate) fn load_actions(&mut self) { for builder in __GPUI_ACTIONS { let action = builder(); - //todo(remove) - let name: SharedString = action.name.into(); - self.builders_by_name.insert(name.clone(), action.build); - self.names_by_type_id.insert(action.type_id, name.clone()); - self.all_names.push(name); + self.insert_action(action); } } + #[cfg(test)] + pub(crate) fn load_action(&mut self) { + self.insert_action(ActionData { + name: A::debug_name(), + type_id: TypeId::of::(), + build: A::build, + }); + } + + fn insert_action(&mut self, action: ActionData) { + let name: SharedString = action.name.into(); + self.builders_by_name.insert(name.clone(), action.build); + self.names_by_type_id.insert(action.type_id, name.clone()); + self.all_names.push(name); + } + /// Construct an action based on its name and optional JSON parameters sourced from the keymap. pub fn build_action_type(&self, type_id: &TypeId) -> Result> { let name = self diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 81f66746c536e73a716b5c5559f4385fcbe5d70b..c65b169596c8b8627521e029d61ae59e24b3845b 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,8 +192,9 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 0..context_stack.len() { - let context = &context_stack[0..=i]; + for i in 1..context_stack.len() { + dbg!(i); + let context = &context_stack[0..i]; if keymap.binding_enabled(binding, context) { return true; } @@ -283,3 +284,86 @@ impl DispatchTree { *self.node_stack.last().unwrap() } } + +#[cfg(test)] +mod tests { + use std::{rc::Rc, sync::Arc}; + + use parking_lot::Mutex; + + use crate::{Action, ActionRegistry, DispatchTree, KeyBinding, KeyContext, Keymap}; + + #[derive(PartialEq, Eq)] + struct TestAction; + + impl Action for TestAction { + fn name(&self) -> &'static str { + "test::TestAction" + } + + fn debug_name() -> &'static str + where + Self: ::std::marker::Sized, + { + "test::TestAction" + } + + fn partial_eq(&self, action: &dyn Action) -> bool { + action + .as_any() + .downcast_ref::() + .map_or(false, |a| self == a) + } + + fn boxed_clone(&self) -> std::boxed::Box { + Box::new(TestAction) + } + + fn as_any(&self) -> &dyn ::std::any::Any { + self + } + + fn build(_value: serde_json::Value) -> anyhow::Result> + where + Self: Sized, + { + Ok(Box::new(TestAction)) + } + } + + #[test] + fn test_keybinding_for_action_bounds() { + dbg!("got here"); + + let keymap = Keymap::new(vec![KeyBinding::new( + "cmd-n", + TestAction, + Some("ProjectPanel"), + )]); + dbg!("got here"); + + let mut registry = ActionRegistry::default(); + dbg!("got here"); + + registry.load_action::(); + + dbg!("got here"); + + let keymap = Arc::new(Mutex::new(keymap)); + dbg!("got here"); + + let tree = DispatchTree::new(keymap, Rc::new(registry)); + + dbg!("got here"); + let keybinding = tree.bindings_for_action( + &TestAction, + &vec![ + KeyContext::parse(",").unwrap(), + KeyContext::parse("Workspace").unwrap(), + KeyContext::parse("ProjectPanel").unwrap(), + ], + ); + + assert!(keybinding[0].action.partial_eq(&TestAction)) + } +} From 4afa5fb23e6c791bde56c23fa6c57a19fb233395 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 8 Jan 2024 21:54:59 -0500 Subject: [PATCH 014/334] Add stories for collab notifications (#3967) This PR adds some basic stories for collab notifications to make them easier to work on: Screenshot 2024-01-08 at 9 43 39 PM I factored out a `CollabNotification` component that defines the general structure for one of these notifications, and this is the component that we use in the stories, with representative values passed to it to simulate the different instances of the notification. We can't use the actual notification components in the stories due to their data dependencies. Release Notes: - N/A --- Cargo.lock | 2 + crates/collab_ui/Cargo.toml | 3 + crates/collab_ui/src/notifications.rs | 11 ++- .../src/notifications/collab_notification.rs | 52 +++++++++++++ .../incoming_call_notification.rs | 52 +++++-------- .../project_shared_notification.rs | 74 +++++++------------ crates/collab_ui/src/notifications/stories.rs | 3 + .../stories/collab_notification.rs | 50 +++++++++++++ crates/storybook/Cargo.toml | 1 + crates/storybook/src/story_selector.rs | 4 + 10 files changed, 169 insertions(+), 83 deletions(-) create mode 100644 crates/collab_ui/src/notifications/collab_notification.rs create mode 100644 crates/collab_ui/src/notifications/stories.rs create mode 100644 crates/collab_ui/src/notifications/stories/collab_notification.rs diff --git a/Cargo.lock b/Cargo.lock index 00c13d10ff899169f469d0fbe233b9e75a3649f1..c6e7ecebc1300628462750ed714b9103e6a001c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1559,6 +1559,7 @@ dependencies = [ "serde_json", "settings", "smallvec", + "story", "theme", "theme_selector", "time", @@ -7447,6 +7448,7 @@ dependencies = [ "backtrace-on-stack-overflow", "chrono", "clap 4.4.4", + "collab_ui", "dialoguer", "editor", "fuzzy", diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index f845de3a939886fefa42343131e0c4ec18543fea..84c1810bc841d904a7a534fb562671dc9d7232c9 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -9,6 +9,8 @@ path = "src/collab_ui.rs" doctest = false [features] +default = [] +stories = ["dep:story"] test-support = [ "call/test-support", "client/test-support", @@ -44,6 +46,7 @@ project = { path = "../project" } recent_projects = { path = "../recent_projects" } rpc = { path = "../rpc" } settings = { path = "../settings" } +story = { path = "../story", optional = true } feature_flags = { path = "../feature_flags"} theme = { path = "../theme" } theme_selector = { path = "../theme_selector" } diff --git a/crates/collab_ui/src/notifications.rs b/crates/collab_ui/src/notifications.rs index 5c184ec5c86ab268f4455b21d855ca118d40d50b..7759fef52059fb47dbc74a3803be309dded114c1 100644 --- a/crates/collab_ui/src/notifications.rs +++ b/crates/collab_ui/src/notifications.rs @@ -1,9 +1,16 @@ +mod collab_notification; +pub mod incoming_call_notification; +pub mod project_shared_notification; + +#[cfg(feature = "stories")] +mod stories; + use gpui::AppContext; use std::sync::Arc; use workspace::AppState; -pub mod incoming_call_notification; -pub mod project_shared_notification; +#[cfg(feature = "stories")] +pub use stories::*; pub fn init(app_state: &Arc, cx: &mut AppContext) { incoming_call_notification::init(app_state, cx); diff --git a/crates/collab_ui/src/notifications/collab_notification.rs b/crates/collab_ui/src/notifications/collab_notification.rs new file mode 100644 index 0000000000000000000000000000000000000000..e2ef06f9a58f8b131f166ab2a83250839f076ca2 --- /dev/null +++ b/crates/collab_ui/src/notifications/collab_notification.rs @@ -0,0 +1,52 @@ +use gpui::{img, prelude::*, AnyElement}; +use smallvec::SmallVec; +use ui::prelude::*; + +#[derive(IntoElement)] +pub struct CollabNotification { + avatar_uri: SharedString, + accept_button: Button, + dismiss_button: Button, + children: SmallVec<[AnyElement; 2]>, +} + +impl CollabNotification { + pub fn new( + avatar_uri: impl Into, + accept_button: Button, + dismiss_button: Button, + ) -> Self { + Self { + avatar_uri: avatar_uri.into(), + accept_button, + dismiss_button, + children: SmallVec::new(), + } + } +} + +impl ParentElement for CollabNotification { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + &mut self.children + } +} + +impl RenderOnce for CollabNotification { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + h_stack() + .text_ui() + .justify_between() + .size_full() + .overflow_hidden() + .elevation_3(cx) + .p_2() + .gap_2() + .child(img(self.avatar_uri).w_12().h_12().rounded_full()) + .child(v_stack().overflow_hidden().children(self.children)) + .child( + v_stack() + .child(self.accept_button) + .child(self.dismiss_button), + ) + } +} diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index fa28ef9a6030d4009e96d170e7f7aefc46e52783..223415119fca00cca478d56423b20f9b44417c2e 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -1,15 +1,12 @@ use crate::notification_window_options; +use crate::notifications::collab_notification::CollabNotification; use call::{ActiveCall, IncomingCall}; use futures::StreamExt; -use gpui::{ - img, px, AppContext, ParentElement, Render, RenderOnce, Styled, ViewContext, - VisualContext as _, WindowHandle, -}; +use gpui::{prelude::*, AppContext, WindowHandle}; use settings::Settings; use std::sync::{Arc, Weak}; use theme::ThemeSettings; -use ui::prelude::*; -use ui::{h_stack, v_stack, Button, Label}; +use ui::{prelude::*, Button, Label}; use util::ResultExt; use workspace::AppState; @@ -31,8 +28,8 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { if let Some(incoming_call) = incoming_call { let unique_screens = cx.update(|cx| cx.displays()).unwrap(); let window_size = gpui::Size { - width: px(380.), - height: px(64.), + width: px(400.), + height: px(72.), }; for screen in unique_screens { @@ -129,35 +126,22 @@ impl Render for IncomingCallNotification { cx.set_rem_size(ui_font_size); - h_stack() - .font(ui_font) - .text_ui() - .justify_between() - .size_full() - .overflow_hidden() - .elevation_3(cx) - .p_2() - .gap_2() - .child( - img(self.state.call.calling_user.avatar_uri.clone()) - .w_12() - .h_12() - .rounded_full(), + div().size_full().font(ui_font).child( + CollabNotification::new( + self.state.call.calling_user.avatar_uri.clone(), + Button::new("accept", "Accept").on_click({ + let state = self.state.clone(); + move |_, cx| state.respond(true, cx) + }), + Button::new("decline", "Decline").on_click({ + let state = self.state.clone(); + move |_, cx| state.respond(false, cx) + }), ) .child(v_stack().overflow_hidden().child(Label::new(format!( "{} is sharing a project in Zed", self.state.call.calling_user.github_login - )))) - .child( - v_stack() - .child(Button::new("accept", "Accept").render(cx).on_click({ - let state = self.state.clone(); - move |_, cx| state.respond(true, cx) - })) - .child(Button::new("decline", "Decline").render(cx).on_click({ - let state = self.state.clone(); - move |_, cx| state.respond(false, cx) - })), - ) + )))), + ) } } diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 982214c3e596e7290a4837264cc570c5aa786c94..79adc69a801dbe5ed30c7b9afb8d1df19bba6c5e 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -1,12 +1,13 @@ use crate::notification_window_options; +use crate::notifications::collab_notification::CollabNotification; use call::{room, ActiveCall}; use client::User; use collections::HashMap; -use gpui::{img, px, AppContext, ParentElement, Render, Size, Styled, ViewContext, VisualContext}; +use gpui::{AppContext, Size}; use settings::Settings; use std::sync::{Arc, Weak}; use theme::ThemeSettings; -use ui::{h_stack, prelude::*, v_stack, Button, Label}; +use ui::{prelude::*, Button, Label}; use workspace::AppState; pub fn init(app_state: &Arc, cx: &mut AppContext) { @@ -130,51 +131,30 @@ impl Render for ProjectSharedNotification { cx.set_rem_size(ui_font_size); - h_stack() - .font(ui_font) - .text_ui() - .justify_between() - .size_full() - .overflow_hidden() - .elevation_3(cx) - .p_2() - .gap_2() - .child( - img(self.owner.avatar_uri.clone()) - .w_12() - .h_12() - .rounded_full(), - ) - .child( - v_stack() - .overflow_hidden() - .child(Label::new(self.owner.github_login.clone())) - .child(Label::new(format!( - "is sharing a project in Zed{}", - if self.worktree_root_names.is_empty() { - "" - } else { - ":" - } - ))) - .children(if self.worktree_root_names.is_empty() { - None - } else { - Some(Label::new(self.worktree_root_names.join(", "))) - }), - ) - .child( - v_stack() - .child(Button::new("open", "Open").on_click(cx.listener( - move |this, _event, cx| { - this.join(cx); - }, - ))) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( - move |this, _event, cx| { - this.dismiss(cx); - }, - ))), + div().size_full().font(ui_font).child( + CollabNotification::new( + self.owner.avatar_uri.clone(), + Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| { + this.join(cx); + })), + Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| { + this.dismiss(cx); + })), ) + .child(Label::new(self.owner.github_login.clone())) + .child(Label::new(format!( + "is sharing a project in Zed{}", + if self.worktree_root_names.is_empty() { + "" + } else { + ":" + } + ))) + .children(if self.worktree_root_names.is_empty() { + None + } else { + Some(Label::new(self.worktree_root_names.join(", "))) + }), + ) } } diff --git a/crates/collab_ui/src/notifications/stories.rs b/crates/collab_ui/src/notifications/stories.rs new file mode 100644 index 0000000000000000000000000000000000000000..36518679c661627346e45357161ba117a6bfe5b3 --- /dev/null +++ b/crates/collab_ui/src/notifications/stories.rs @@ -0,0 +1,3 @@ +mod collab_notification; + +pub use collab_notification::*; diff --git a/crates/collab_ui/src/notifications/stories/collab_notification.rs b/crates/collab_ui/src/notifications/stories/collab_notification.rs new file mode 100644 index 0000000000000000000000000000000000000000..c43cac46d21352ac8375e33cf530891385c36da0 --- /dev/null +++ b/crates/collab_ui/src/notifications/stories/collab_notification.rs @@ -0,0 +1,50 @@ +use gpui::prelude::*; +use story::{StoryContainer, StoryItem, StorySection}; +use ui::prelude::*; + +use crate::notifications::collab_notification::CollabNotification; + +pub struct CollabNotificationStory; + +impl Render for CollabNotificationStory { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + let window_container = |width, height| div().w(px(width)).h(px(height)); + + StoryContainer::new( + "CollabNotification Story", + "crates/collab_ui/src/notifications/stories/collab_notification.rs", + ) + .child( + StorySection::new().child(StoryItem::new( + "Incoming Call Notification", + window_container(400., 72.).child( + CollabNotification::new( + "https://avatars.githubusercontent.com/u/1486634?v=4", + Button::new("accept", "Accept"), + Button::new("decline", "Decline"), + ) + .child( + v_stack() + .overflow_hidden() + .child(Label::new("maxdeviant is sharing a project in Zed")), + ), + ), + )), + ) + .child( + StorySection::new().child(StoryItem::new( + "Project Shared Notification", + window_container(400., 72.).child( + CollabNotification::new( + "https://avatars.githubusercontent.com/u/1714999?v=4", + Button::new("open", "Open"), + Button::new("dismiss", "Dismiss"), + ) + .child(Label::new("iamnbutler")) + .child(Label::new("is sharing a project in Zed:")) + .child(Label::new("zed")), + ), + )), + ) + } +} diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index 033b3fa8d9077ee9866f0a0a7e382d5f68d3e483..9f08556757b6b59d1aed6fa7361029a36664a40a 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -14,6 +14,7 @@ anyhow.workspace = true backtrace-on-stack-overflow = "0.3.0" chrono = "0.4" clap = { version = "4.4", features = ["derive", "string"] } +collab_ui = { path = "../collab_ui", features = ["stories"] } strum = { version = "0.25.0", features = ["derive"] } dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } editor = { path = "../editor" } diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 27ddfe26ac2884cd327f065c01bdf8bf7617e624..120e60d34a1499ed143394713e0c46fbf1bc2dfa 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -16,6 +16,7 @@ pub enum ComponentStory { Avatar, Button, Checkbox, + CollabNotification, ContextMenu, Cursor, Disclosure, @@ -45,6 +46,9 @@ impl ComponentStory { Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(), Self::Button => cx.new_view(|_| ui::ButtonStory).into(), Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(), + Self::CollabNotification => cx + .new_view(|_| collab_ui::notifications::CollabNotificationStory) + .into(), Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(), Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(), Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(), From 844d161c400e5f4e41f56743d010791314d16c0f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 16:52:20 -0700 Subject: [PATCH 015/334] Allow adding write access to guests --- Cargo.lock | 2 + crates/call/src/room.rs | 23 ++- crates/collab/Cargo.toml | 2 + crates/collab/src/db/queries/rooms.rs | 40 +++++ crates/collab/src/rpc.rs | 22 +++ .../collab/src/tests/channel_guest_tests.rs | 108 +++++++----- crates/collab/src/tests/test_server.rs | 63 ++++++- crates/collab_ui/src/collab_panel.rs | 166 ++++++++---------- crates/gpui/src/app/test_context.rs | 4 + crates/language/src/buffer.rs | 6 + crates/multi_buffer/src/multi_buffer.rs | 7 +- crates/project/src/project.rs | 18 +- crates/rpc/proto/zed.proto | 9 +- crates/rpc/src/proto.rs | 2 + crates/vim/src/test/vim_test_context.rs | 1 - crates/workspace/src/notifications.rs | 17 +- 16 files changed, 347 insertions(+), 143 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00c13d10ff899169f469d0fbe233b9e75a3649f1..4249503e72bf51356c1b3fc28893c27d74cc6410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1473,6 +1473,7 @@ dependencies = [ "editor", "env_logger", "envy", + "file_finder", "fs", "futures 0.3.28", "git", @@ -1486,6 +1487,7 @@ dependencies = [ "live_kit_server", "log", "lsp", + "menu", "nanoid", "node_runtime", "notifications", diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index e2c9bf5886faca374334e463b8d0ac4711ce863b..6f8f8e171793931d64948e2972536eb83c739214 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -620,6 +620,27 @@ impl Room { self.local_participant.role == proto::ChannelRole::Admin } + pub fn set_participant_role( + &mut self, + user_id: u64, + role: proto::ChannelRole, + cx: &ModelContext, + ) -> Task> { + let client = self.client.clone(); + let room_id = self.id; + let role = role.into(); + cx.spawn(|_, _| async move { + client + .request(proto::SetRoomParticipantRole { + room_id, + user_id, + role, + }) + .await + .map(|_| ()) + }) + } + pub fn pending_participants(&self) -> &[Arc] { &self.pending_participants } @@ -731,7 +752,7 @@ impl Room { this.joined_projects.retain(|project| { if let Some(project) = project.upgrade() { - project.update(cx, |project, _| project.set_role(role)); + project.update(cx, |project, cx| project.set_role(role, cx)); true } else { false diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index baf279d6340d5024f18cdd1beb863aa76c549326..13bbd0cf2511d8b91015fd167842d82ed7b12df4 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -74,6 +74,8 @@ live_kit_client = { path = "../live_kit_client", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } node_runtime = { path = "../node_runtime" } notifications = { path = "../notifications", features = ["test-support"] } +file_finder = { path = "../file_finder"} +menu = { path = "../menu"} project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 9a87f91b8182b69b32539810e9fc46f7afdadb14..c3e71880fdc5e8eb871dfd9a9aff3e85251a321e 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -1004,6 +1004,46 @@ impl Database { .await } + pub async fn set_room_participant_role( + &self, + admin_id: UserId, + room_id: RoomId, + user_id: UserId, + role: ChannelRole, + ) -> Result> { + self.room_transaction(room_id, |tx| async move { + room_participant::Entity::find() + .filter( + Condition::all() + .add(room_participant::Column::RoomId.eq(room_id)) + .add(room_participant::Column::UserId.eq(admin_id)) + .add(room_participant::Column::Role.eq(ChannelRole::Admin)), + ) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("only admins can set participant role"))?; + + let result = room_participant::Entity::update_many() + .filter( + Condition::all() + .add(room_participant::Column::RoomId.eq(room_id)) + .add(room_participant::Column::UserId.eq(user_id)), + ) + .set(room_participant::ActiveModel { + role: ActiveValue::set(Some(ChannelRole::from(role))), + ..Default::default() + }) + .exec(&*tx) + .await?; + + if result.rows_affected != 1 { + Err(anyhow!("could not update room participant role"))?; + } + Ok(self.get_room(room_id, &tx).await?) + }) + .await + } + pub async fn connection_lost(&self, connection: ConnectionId) -> Result<()> { self.transaction(|tx| async move { self.room_connection_lost(connection, &*tx).await?; diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5301ca9a23b0a3bd3d0cce44de7eaa640449347e..766cf4bcc0d7344c0780c0a34b62ac4feeb5ea84 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -202,6 +202,7 @@ impl Server { .add_request_handler(join_room) .add_request_handler(rejoin_room) .add_request_handler(leave_room) + .add_request_handler(set_room_participant_role) .add_request_handler(call) .add_request_handler(cancel_call) .add_message_handler(decline_call) @@ -1258,6 +1259,27 @@ async fn leave_room( Ok(()) } +async fn set_room_participant_role( + request: proto::SetRoomParticipantRole, + response: Response, + session: Session, +) -> Result<()> { + let room = session + .db() + .await + .set_room_participant_role( + session.user_id, + RoomId::from_proto(request.room_id), + UserId::from_proto(request.user_id), + ChannelRole::from(request.role()), + ) + .await?; + + room_updated(&room, &session.peer); + response.send(proto::Ack {})?; + Ok(()) +} + async fn call( request: proto::Call, response: Response, diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 32cc074ec96a11e3806a256eedd8f74191689ace..45e9e5a1d7588b26fc314f0293423af5803d6166 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -1,5 +1,6 @@ use crate::tests::TestServer; use call::ActiveCall; +use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use rpc::proto; use workspace::Workspace; @@ -13,37 +14,18 @@ async fn test_channel_guests( let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; + let active_call_a = cx_a.read(ActiveCall::global); let channel_id = server - .make_channel("the-channel", None, (&client_a, cx_a), &mut []) - .await; - - client_a - .channel_store() - .update(cx_a, |channel_store, cx| { - channel_store.set_channel_visibility(channel_id, proto::ChannelVisibility::Public, cx) - }) - .await - .unwrap(); - - client_a - .fs() - .insert_tree( - "/a", - serde_json::json!({ - "a.txt": "a-contents", - }), - ) + .make_public_channel("the-channel", &client_a, cx_a) .await; - let active_call_a = cx_a.read(ActiveCall::global); - // Client A shares a project in the channel + let project_a = client_a.build_test_project(cx_a).await; active_call_a .update(cx_a, |call, cx| call.join_channel(channel_id, cx)) .await .unwrap(); - let (project_a, _) = client_a.build_local_project("/a", cx_a).await; let project_id = active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await @@ -57,33 +39,16 @@ async fn test_channel_guests( // b should be following a in the shared project. // B is a guest, - cx_a.executor().run_until_parked(); - - // todo!() the test window does not call activation handlers - // correctly yet, so this API does not work. - // let project_b = active_call_b.read_with(cx_b, |call, _| { - // call.location() - // .unwrap() - // .upgrade() - // .expect("should not be weak") - // }); - - let window_b = cx_b.update(|cx| cx.active_window().unwrap()); - let cx_b = &mut VisualTestContext::from_window(window_b, cx_b); - - let workspace_b = window_b - .downcast::() - .unwrap() - .root_view(cx_b) - .unwrap(); - let project_b = workspace_b.update(cx_b, |workspace, _| workspace.project().clone()); + executor.run_until_parked(); + let active_call_b = cx_b.read(ActiveCall::global); + let project_b = + active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); assert_eq!( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), ); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); - assert!(project_b .update(cx_b, |project, cx| { let worktree_id = project.worktrees().next().unwrap().read(cx).id(); @@ -92,3 +57,60 @@ async fn test_channel_guests( .await .is_err()) } + +#[gpui::test] +async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + + let channel_id = server + .make_public_channel("the-channel", &client_a, cx_a) + .await; + + let project_a = client_a.build_test_project(cx_a).await; + cx_a.update(|cx| workspace::join_channel(channel_id, client_a.app_state.clone(), None, cx)) + .await + .unwrap(); + + // Client A shares a project in the channel + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + cx_a.run_until_parked(); + + // Client B joins channel A as a guest + cx_b.update(|cx| workspace::join_channel(channel_id, client_b.app_state.clone(), None, cx)) + .await + .unwrap(); + cx_a.run_until_parked(); + + let project_b = + active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); + + // client B opens 1.txt + let (workspace, cx_b) = client_b.active_workspace(cx_b); + cx_b.simulate_keystrokes("cmd-p 1 enter"); + let editor_b = cx_b.update(|cx| workspace.read(cx).active_item_as::(cx).unwrap()); + + active_call_a + .update(cx_a, |call, cx| { + call.room().unwrap().update(cx, |room, cx| { + room.set_participant_role( + client_b.user_id().unwrap(), + proto::ChannelRole::Member, + cx, + ) + }) + }) + .await + .unwrap(); + + cx_a.run_until_parked(); + + assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); + assert!(!editor_b.update(cx_b, |e, cx| e.read_only(cx))); +} diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 034a85961f8e98966a2764c4196682fabf4b33cf..6d8bb05d670c1448d94dc398d0ef5a069528a3f4 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -20,7 +20,11 @@ use node_runtime::FakeNodeRuntime; use notifications::NotificationStore; use parking_lot::Mutex; use project::{Project, WorktreeId}; -use rpc::{proto::ChannelRole, RECEIVE_TIMEOUT}; +use rpc::{ + proto::{self, ChannelRole}, + RECEIVE_TIMEOUT, +}; +use serde_json::json; use settings::SettingsStore; use std::{ cell::{Ref, RefCell, RefMut}, @@ -228,12 +232,16 @@ impl TestServer { Project::init(&client, cx); client::init(&client, cx); language::init(cx); - editor::init_settings(cx); + editor::init(cx); workspace::init(app_state.clone(), cx); audio::init((), cx); call::init(client.clone(), user_store.clone(), cx); channel::init(&client, user_store.clone(), cx); notifications::init(client.clone(), user_store, cx); + collab_ui::init(&app_state, cx); + file_finder::init(cx); + menu::init(); + settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap(); }); client @@ -351,6 +359,31 @@ impl TestServer { channel_id } + pub async fn make_public_channel( + &self, + channel: &str, + client: &TestClient, + cx: &mut TestAppContext, + ) -> u64 { + let channel_id = self + .make_channel(channel, None, (client, cx), &mut []) + .await; + + client + .channel_store() + .update(cx, |channel_store, cx| { + channel_store.set_channel_visibility( + channel_id, + proto::ChannelVisibility::Public, + cx, + ) + }) + .await + .unwrap(); + + channel_id + } + pub async fn make_channel_tree( &self, channels: &[(&str, Option<&str>)], @@ -580,6 +613,20 @@ impl TestClient { (project, worktree.read_with(cx, |tree, _| tree.id())) } + pub async fn build_test_project(&self, cx: &mut TestAppContext) -> Model { + self.fs() + .insert_tree( + "/a", + json!({ + "1.txt": "one\none\none", + "2.txt": "two\ntwo\ntwo", + "3.txt": "three\nthree\nthree", + }), + ) + .await; + self.build_local_project("/a", cx).await.0 + } + pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model { cx.update(|cx| { Project::local( @@ -619,6 +666,18 @@ impl TestClient { ) -> (View, &'a mut VisualTestContext) { cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx)) } + + pub fn active_workspace<'a>( + &'a self, + cx: &'a mut TestAppContext, + ) -> (View, &'a mut VisualTestContext) { + let window = cx.update(|cx| cx.active_window().unwrap().downcast::().unwrap()); + + let view = window.root_view(cx).unwrap(); + let cx = Box::new(VisualTestContext::from_window(*window.deref(), cx)); + // it might be nice to try and cleanup these at the end of each test. + (view, Box::leak(cx)) + } } impl Drop for TestClient { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 86d0131d70afa9dbf0a9c51d6943dcb1f1ad2f80..e25ad0d225680e8454ae69ce063a1fb5d67d1b2a 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -37,7 +37,7 @@ use ui::{ use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, - notifications::NotifyResultExt, + notifications::{NotifyResultExt, NotifyTaskExt}, Workspace, }; @@ -140,6 +140,7 @@ enum ListEntry { user: Arc, peer_id: Option, is_pending: bool, + role: proto::ChannelRole, }, ParticipantProject { project_id: u64, @@ -151,10 +152,6 @@ enum ListEntry { peer_id: Option, is_last: bool, }, - GuestCount { - count: usize, - has_visible_participants: bool, - }, IncomingRequest(Arc), OutgoingRequest(Arc), ChannelInvite(Arc), @@ -384,14 +381,10 @@ impl CollabPanel { if !self.collapsed_sections.contains(&Section::ActiveCall) { let room = room.read(cx); - let mut guest_count_ix = 0; - let mut guest_count = if room.read_only() { 1 } else { 0 }; - let mut non_guest_count = if room.read_only() { 0 } else { 1 }; if let Some(channel_id) = room.channel_id() { self.entries.push(ListEntry::ChannelNotes { channel_id }); self.entries.push(ListEntry::ChannelChat { channel_id }); - guest_count_ix = self.entries.len(); } // Populate the active user. @@ -410,12 +403,13 @@ impl CollabPanel { &Default::default(), executor.clone(), )); - if !matches.is_empty() && !room.read_only() { + if !matches.is_empty() { let user_id = user.id; self.entries.push(ListEntry::CallParticipant { user, peer_id: None, is_pending: false, + role: room.local_participant().role, }); let mut projects = room.local_participant().projects.iter().peekable(); while let Some(project) = projects.next() { @@ -442,12 +436,6 @@ impl CollabPanel { room.remote_participants() .iter() .filter_map(|(_, participant)| { - if participant.role == proto::ChannelRole::Guest { - guest_count += 1; - return None; - } else { - non_guest_count += 1; - } Some(StringMatchCandidate { id: participant.user.id as usize, string: participant.user.github_login.clone(), @@ -455,7 +443,7 @@ impl CollabPanel { }) }), ); - let matches = executor.block(match_strings( + let mut matches = executor.block(match_strings( &self.match_candidates, &query, true, @@ -463,6 +451,15 @@ impl CollabPanel { &Default::default(), executor.clone(), )); + matches.sort_by(|a, b| { + let a_is_guest = room.role_for_user(a.candidate_id as u64) + == Some(proto::ChannelRole::Guest); + let b_is_guest = room.role_for_user(b.candidate_id as u64) + == Some(proto::ChannelRole::Guest); + a_is_guest + .cmp(&b_is_guest) + .then_with(|| a.string.cmp(&b.string)) + }); for mat in matches { let user_id = mat.candidate_id as u64; let participant = &room.remote_participants()[&user_id]; @@ -470,6 +467,7 @@ impl CollabPanel { user: participant.user.clone(), peer_id: Some(participant.peer_id), is_pending: false, + role: participant.role, }); let mut projects = participant.projects.iter().peekable(); while let Some(project) = projects.next() { @@ -488,15 +486,6 @@ impl CollabPanel { }); } } - if guest_count > 0 { - self.entries.insert( - guest_count_ix, - ListEntry::GuestCount { - count: guest_count, - has_visible_participants: non_guest_count > 0, - }, - ); - } // Populate pending participants. self.match_candidates.clear(); @@ -521,6 +510,7 @@ impl CollabPanel { user: room.pending_participants()[mat.candidate_id].clone(), peer_id: None, is_pending: true, + role: proto::ChannelRole::Member, })); } } @@ -834,13 +824,19 @@ impl CollabPanel { user: &Arc, peer_id: Option, is_pending: bool, + role: proto::ChannelRole, is_selected: bool, cx: &mut ViewContext, ) -> ListItem { + let user_id = user.id; let is_current_user = - self.user_store.read(cx).current_user().map(|user| user.id) == Some(user.id); + self.user_store.read(cx).current_user().map(|user| user.id) == Some(user_id); let tooltip = format!("Follow {}", user.github_login); + let is_call_admin = ActiveCall::global(cx).read(cx).room().is_some_and(|room| { + room.read(cx).local_participant().role == proto::ChannelRole::Admin + }); + ListItem::new(SharedString::from(user.github_login.clone())) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) @@ -853,17 +849,27 @@ impl CollabPanel { .on_click(move |_, cx| Self::leave_call(cx)) .tooltip(|cx| Tooltip::text("Leave Call", cx)) .into_any_element() + } else if role == proto::ChannelRole::Guest { + Label::new("Guest").color(Color::Muted).into_any_element() } else { div().into_any_element() }) - .when_some(peer_id, |this, peer_id| { - this.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) + .when_some(peer_id, |el, peer_id| { + if role == proto::ChannelRole::Guest { + return el; + } + el.tooltip(move |cx| Tooltip::text(tooltip.clone(), cx)) .on_click(cx.listener(move |this, _, cx| { this.workspace .update(cx, |workspace, cx| workspace.follow(peer_id, cx)) .ok(); })) }) + .when(is_call_admin && role == proto::ChannelRole::Guest, |el| { + el.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { + this.deploy_participant_context_menu(event.position, user_id, cx) + })) + }) } fn render_participant_project( @@ -986,41 +992,6 @@ impl CollabPanel { .tooltip(move |cx| Tooltip::text("Open Chat", cx)) } - fn render_guest_count( - &self, - count: usize, - has_visible_participants: bool, - is_selected: bool, - cx: &mut ViewContext, - ) -> impl IntoElement { - let manageable_channel_id = ActiveCall::global(cx).read(cx).room().and_then(|room| { - let room = room.read(cx); - if room.local_participant_is_admin() { - room.channel_id() - } else { - None - } - }); - - ListItem::new("guest_count") - .selected(is_selected) - .start_slot( - h_stack() - .gap_1() - .child(render_tree_branch(!has_visible_participants, false, cx)) - .child(""), - ) - .child(Label::new(if count == 1 { - format!("{} guest", count) - } else { - format!("{} guests", count) - })) - .when_some(manageable_channel_id, |el, channel_id| { - el.tooltip(move |cx| Tooltip::text("Manage Members", cx)) - .on_click(cx.listener(move |this, _, cx| this.manage_members(channel_id, cx))) - }) - } - fn has_subchannels(&self, ix: usize) -> bool { self.entries.get(ix).map_or(false, |entry| { if let ListEntry::Channel { has_children, .. } = entry { @@ -1031,6 +1002,47 @@ impl CollabPanel { }) } + fn deploy_participant_context_menu( + &mut self, + position: Point, + user_id: u64, + cx: &mut ViewContext, + ) { + let this = cx.view().clone(); + + let context_menu = ContextMenu::build(cx, |context_menu, cx| { + context_menu.entry( + "Allow Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role(user_id, proto::ChannelRole::Member, cx) + }) + }) + .detach_and_notify_err(cx) + }), + ) + }); + + cx.focus_view(&context_menu); + let subscription = + cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| { + if this.context_menu.as_ref().is_some_and(|context_menu| { + context_menu.0.focus_handle(cx).contains_focused(cx) + }) { + cx.focus_self(); + } + this.context_menu.take(); + cx.notify(); + }); + self.context_menu = Some((context_menu, position, subscription)); + } + fn deploy_channel_context_menu( &mut self, position: Point, @@ -1242,18 +1254,6 @@ impl CollabPanel { }); } } - ListEntry::GuestCount { .. } => { - let Some(room) = ActiveCall::global(cx).read(cx).room() else { - return; - }; - let room = room.read(cx); - let Some(channel_id) = room.channel_id() else { - return; - }; - if room.local_participant_is_admin() { - self.manage_members(channel_id, cx) - } - } ListEntry::Channel { channel, .. } => { let is_active = maybe!({ let call_channel = ActiveCall::global(cx) @@ -1788,8 +1788,9 @@ impl CollabPanel { user, peer_id, is_pending, + role, } => self - .render_call_participant(user, *peer_id, *is_pending, is_selected, cx) + .render_call_participant(user, *peer_id, *is_pending, *role, is_selected, cx) .into_any_element(), ListEntry::ParticipantProject { project_id, @@ -1809,12 +1810,6 @@ impl CollabPanel { ListEntry::ParticipantScreen { peer_id, is_last } => self .render_participant_screen(*peer_id, *is_last, is_selected, cx) .into_any_element(), - ListEntry::GuestCount { - count, - has_visible_participants, - } => self - .render_guest_count(*count, *has_visible_participants, is_selected, cx) - .into_any_element(), ListEntry::ChannelNotes { channel_id } => self .render_channel_notes(*channel_id, is_selected, cx) .into_any_element(), @@ -2621,11 +2616,6 @@ impl PartialEq for ListEntry { return true; } } - ListEntry::GuestCount { .. } => { - if let ListEntry::GuestCount { .. } = other { - return true; - } - } } false } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 0f71ea61a9ec347b01e601f5e3c1a2237b80e691..6ec37afb0fc2afea5d4e70d6f617c73462170a06 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -238,6 +238,10 @@ impl TestAppContext { } } + pub fn run_until_parked(&mut self) { + self.background_executor.run_until_parked() + } + pub fn dispatch_action(&mut self, window: AnyWindowHandle, action: A) where A: Action, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d9472e8a77afc2dc7222d003aa23f513448ed661..08210875b841cb744b2b9734d9f8c3622da86bc2 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -254,6 +254,7 @@ pub enum Event { LanguageChanged, Reparsed, DiagnosticsUpdated, + CapabilityChanged, Closed, } @@ -631,6 +632,11 @@ impl Buffer { .set_language_registry(language_registry); } + pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext) { + self.capability = capability; + cx.emit(Event::CapabilityChanged) + } + pub fn did_save( &mut self, version: clock::Global, diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 946e6af5ab5fd9a97439edb5acb296972068cf44..f3ecd2d25f2b3866b1f6c3f0ce68415fa7cd53ae 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -80,6 +80,7 @@ pub enum Event { Reloaded, DiffBaseChanged, LanguageChanged, + CapabilityChanged, Reparsed, Saved, FileHandleChanged, @@ -1404,7 +1405,7 @@ impl MultiBuffer { fn on_buffer_event( &mut self, - _: Model, + buffer: Model, event: &language::Event, cx: &mut ModelContext, ) { @@ -1421,6 +1422,10 @@ impl MultiBuffer { language::Event::Reparsed => Event::Reparsed, language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated, language::Event::Closed => Event::Closed, + language::Event::CapabilityChanged => { + self.capability = buffer.read(cx).capability(); + Event::CapabilityChanged + } // language::Event::Operation(_) => return, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 044b750ad9d7cecce1a00f491e0fd66199989bb7..c412fad0e10ba07ee4849273577d6b2425089820 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -799,7 +799,7 @@ impl Project { prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), }; - this.set_role(role); + this.set_role(role, cx); for worktree in worktrees { let _ = this.add_worktree(&worktree, cx); } @@ -1622,14 +1622,22 @@ impl Project { cx.notify(); } - pub fn set_role(&mut self, role: proto::ChannelRole) { - if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { - *capability = if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin - { + pub fn set_role(&mut self, role: proto::ChannelRole, cx: &mut ModelContext) { + let new_capability = + if role == proto::ChannelRole::Member || role == proto::ChannelRole::Admin { Capability::ReadWrite } else { Capability::ReadOnly }; + if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { + if *capability == new_capability { + return; + } + + *capability = new_capability; + } + for buffer in self.opened_buffers() { + buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); } } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index e423441a30218674c4d6b68098a8f724c8a32654..dd5d77440edaaae98876f91a4fc5fa85999dd6ed 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -180,7 +180,8 @@ message Envelope { DeleteNotification delete_notification = 152; MarkNotificationRead mark_notification_read = 153; LspExtExpandMacro lsp_ext_expand_macro = 154; - LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max + LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; + SetRoomParticipantRole set_room_participant_role = 156; // Current max } } @@ -1633,3 +1634,9 @@ message LspExtExpandMacroResponse { string name = 1; string expansion = 2; } + +message SetRoomParticipantRole { + uint64 room_id = 1; + uint64 user_id = 2; + ChannelRole role = 3; +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 25b8b00dae99fe3e4c11677e480c6c48fa9f08c8..5d0a99415480c30969a9a072287841ae6dd6c689 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -283,6 +283,7 @@ messages!( (UsersResponse, Foreground), (LspExtExpandMacro, Background), (LspExtExpandMacroResponse, Background), + (SetRoomParticipantRole, Foreground), ); request_messages!( @@ -367,6 +368,7 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (LspExtExpandMacro, LspExtExpandMacroResponse), + (SetRoomParticipantRole, Ack), ); entity_messages!( diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 5ed5296bff44d3e76c32f2a4b768afd760d1d121..965ac63d381a306a1fdd1c18c0c9d379292461a8 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -17,7 +17,6 @@ pub struct VimTestContext { impl VimTestContext { pub fn init(cx: &mut gpui::TestAppContext) { if cx.has_global::() { - dbg!("OOPS"); return; } cx.update(|cx| { diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 394772b9c45b0a31bf36260389ee2e2fcc7ec717..ac091744c6a6f8f8f76f6cc916ffa19ebba400f7 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -2,7 +2,7 @@ use crate::{Toast, Workspace}; use collections::HashMap; use gpui::{ AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render, - View, ViewContext, VisualContext, + Task, View, ViewContext, VisualContext, WindowContext, }; use std::{any::TypeId, ops::DerefMut}; @@ -393,3 +393,18 @@ where } } } + +pub trait NotifyTaskExt { + fn detach_and_notify_err(self, cx: &mut WindowContext); +} + +impl NotifyTaskExt for Task> +where + E: std::fmt::Debug + 'static, + R: 'static, +{ + fn detach_and_notify_err(self, cx: &mut WindowContext) { + cx.spawn(|mut cx| async move { self.await.notify_async_err(&mut cx) }) + .detach(); + } +} From 8669b08161a8718c3765ad5d217e17f0fdaeed6d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:23:34 -0700 Subject: [PATCH 016/334] Failing test for unmuting microphone --- crates/call/src/room.rs | 6 ++- .../collab/src/tests/channel_guest_tests.rs | 44 ++++++++++++++----- crates/live_kit_client/src/test.rs | 8 ++++ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 6f8f8e171793931d64948e2972536eb83c739214..0a599e85cf4148fd08d902d611d1f3d70b336d39 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -173,7 +173,11 @@ impl Room { cx.spawn(|this, mut cx| async move { connect.await?; - if !cx.update(|cx| Self::mute_on_join(cx))? { + let is_read_only = this + .update(&mut cx, |room, _| room.read_only()) + .unwrap_or(true); + + if !cx.update(|cx| Self::mute_on_join(cx))? && !is_read_only { this.update(&mut cx, |this, cx| this.share_microphone(cx))? .await?; } diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 45e9e5a1d7588b26fc314f0293423af5803d6166..8d21db678bed364cd8504c839bb3c15b339b1cd0 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -3,7 +3,6 @@ use call::ActiveCall; use editor::Editor; use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; use rpc::proto; -use workspace::Workspace; #[gpui::test] async fn test_channel_guests( @@ -44,6 +43,8 @@ async fn test_channel_guests( let active_call_b = cx_b.read(ActiveCall::global); let project_b = active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); + let room_b = active_call_b.update(cx_b, |call, _| call.room().unwrap().clone()); + assert_eq!( project_b.read_with(cx_b, |project, _| project.remote_id()), Some(project_id), @@ -55,7 +56,8 @@ async fn test_channel_guests( project.create_entry((worktree_id, "b.txt"), false, cx) }) .await - .is_err()) + .is_err()); + assert!(room_b.read_with(cx_b, |room, _| !room.is_sharing_mic())); } #[gpui::test] @@ -64,7 +66,6 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); let channel_id = server .make_public_channel("the-channel", &client_a, cx_a) @@ -76,7 +77,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test .unwrap(); // Client A shares a project in the channel - let project_id = active_call_a + active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); @@ -88,14 +89,29 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test .unwrap(); cx_a.run_until_parked(); - let project_b = - active_call_b.read_with(cx_b, |call, _| call.location().unwrap().upgrade().unwrap()); - - // client B opens 1.txt - let (workspace, cx_b) = client_b.active_workspace(cx_b); + // client B opens 1.txt as a guest + let (workspace_b, cx_b) = client_b.active_workspace(cx_b); + let room_b = cx_b + .read(ActiveCall::global) + .update(cx_b, |call, _| call.room().unwrap().clone()); cx_b.simulate_keystrokes("cmd-p 1 enter"); - let editor_b = cx_b.update(|cx| workspace.read(cx).active_item_as::(cx).unwrap()); + let (project_b, editor_b) = workspace_b.update(cx_b, |workspace, cx| { + ( + workspace.project().clone(), + workspace.active_item_as::(cx).unwrap(), + ) + }); + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); + assert!(dbg!( + room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + ) + .is_err()); + + // B is promoted active_call_a .update(cx_a, |call, cx| { call.room().unwrap().update(cx, |room, cx| { @@ -108,9 +124,13 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }) .await .unwrap(); - cx_a.run_until_parked(); + // project and buffers are now editable assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); - assert!(!editor_b.update(cx_b, |e, cx| e.read_only(cx))); + assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx))); + room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .unwrap() } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 1106e66f31e6d3f517fcc21b2fdffd6a7d209a09..07a12a4ab6f5e39df0b672d8fb589497a765fd04 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -167,6 +167,10 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); + if claims.video.can_publish == Some(false) { + return Err(anyhow!("user is not allowed to publish")); + } + let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) @@ -205,6 +209,10 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); + if claims.video.can_publish == Some(false) { + return Err(anyhow!("user is not allowed to publish")); + } + let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) From 82f7dd9bbbee58c81a27219ac9bfd320a3525ecc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:32:12 -0700 Subject: [PATCH 017/334] Prototype cursor sharing (the inefficient way) I think this will be a key user experience driver, but we do need to find a way to enable it without widening our vector clocks. --- crates/collab/src/db/queries/projects.rs | 8 +++++--- crates/collab/src/rpc.rs | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 6e1bf16309bd69315903a37ce7b57d389e749161..5b98228f112fd3a7072529dcd31dd915098e79c7 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -883,6 +883,7 @@ impl Database { &self, project_id: ProjectId, connection_id: ConnectionId, + requires_write: bool, ) -> Result>> { let room_id = self.room_id_for_project(project_id).await?; self.room_transaction(room_id, |tx| async move { @@ -893,9 +894,10 @@ impl Database { .await? .ok_or_else(|| anyhow!("no such room"))?; - if !current_participant - .role - .map_or(false, |role| role.can_edit_projects()) + if requires_write + && !current_participant + .role + .map_or(false, |role| role.can_edit_projects()) { Err(anyhow!("not authorized to edit projects"))?; } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5301ca9a23b0a3bd3d0cce44de7eaa640449347e..987c6dbffef43581112c951b3df9e45b11472c82 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1814,11 +1814,24 @@ async fn update_buffer( let mut guest_connection_ids; let mut host_connection_id = None; + let mut requires_write_permission = false; + + for op in request.operations.iter() { + match op.variant { + None | Some(proto::operation::Variant::UpdateSelections(_)) => {} + Some(_) => requires_write_permission = true, + } + } + { let collaborators = session .db() .await - .project_collaborators_for_buffer_update(project_id, session.connection_id) + .project_collaborators_for_buffer_update( + project_id, + session.connection_id, + requires_write_permission, + ) .await?; guest_connection_ids = Vec::with_capacity(collaborators.len() - 1); for collaborator in collaborators.iter() { From 0f7b47af390eb04bd82fa24d0b285d0917411907 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 8 Jan 2024 22:45:15 -0700 Subject: [PATCH 018/334] Run migations on development server start --- crates/collab/src/lib.rs | 6 ++++++ crates/collab/src/main.rs | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 85216525b0018c6d051c55a5882af8445f45c7d0..68fbb4e4d7d48286564da1ab7cc6359cea819dcd 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -103,6 +103,12 @@ pub struct Config { pub zed_environment: String, } +impl Config { + pub fn is_development(&self) -> bool { + self.zed_environment == "development" + } +} + #[derive(Default, Deserialize)] pub struct MigrateConfig { pub database_url: String, diff --git a/crates/collab/src/main.rs b/crates/collab/src/main.rs index 6fbb451fee71a01c1e41c123523715d2aae61f65..87df7cac6fc77f4204d00c4df8a759d141b0345d 100644 --- a/crates/collab/src/main.rs +++ b/crates/collab/src/main.rs @@ -53,6 +53,25 @@ async fn main() -> Result<()> { let config = envy::from_env::().expect("error loading config"); init_tracing(&config); + if config.is_development() { + // sanity check database url so even if we deploy a busted ZED_ENVIRONMENT to production + // we do not run + if config.database_url != "postgres://postgres@localhost/zed" { + panic!("about to run development migrations on a non-development database?") + } + let migrations_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations")); + let db_options = db::ConnectOptions::new(config.database_url.clone()); + let db = Database::new(db_options, Executor::Production).await?; + + let migrations = db.migrate(&migrations_path, false).await?; + for (migration, duration) in migrations { + println!( + "Ran {} {} {:?}", + migration.version, migration.description, duration + ); + } + } + let state = AppState::new(config).await?; let listener = TcpListener::bind(&format!("0.0.0.0:{}", state.config.http_port)) From aa1d2d2f24c666536d6d57599cdabcf6238b85cd Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 10:08:27 +0200 Subject: [PATCH 019/334] Remove dbg! usage from tests --- .../src/syntax_map/syntax_map_tests.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/language/src/syntax_map/syntax_map_tests.rs b/crates/language/src/syntax_map/syntax_map_tests.rs index f20f481613eabfffddee791873d78d6383086ade..8b9169d1cce47f96cd9fb6a768d5f3b5397ed4d4 100644 --- a/crates/language/src/syntax_map/syntax_map_tests.rs +++ b/crates/language/src/syntax_map/syntax_map_tests.rs @@ -258,19 +258,19 @@ fn test_typing_multiple_new_injections() { let (buffer, syntax_map) = test_edit_sequence( "Rust", &[ - "fn a() { dbg }", - "fn a() { dbg«!» }", - "fn a() { dbg!«()» }", - "fn a() { dbg!(«b») }", - "fn a() { dbg!(b«.») }", - "fn a() { dbg!(b.«c») }", - "fn a() { dbg!(b.c«()») }", - "fn a() { dbg!(b.c(«vec»)) }", - "fn a() { dbg!(b.c(vec«!»)) }", - "fn a() { dbg!(b.c(vec!«[]»)) }", - "fn a() { dbg!(b.c(vec![«d»])) }", - "fn a() { dbg!(b.c(vec![d«.»])) }", - "fn a() { dbg!(b.c(vec![d.«e»])) }", + "fn a() { test_macro }", + "fn a() { test_macro«!» }", + "fn a() { test_macro!«()» }", + "fn a() { test_macro!(«b») }", + "fn a() { test_macro!(b«.») }", + "fn a() { test_macro!(b.«c») }", + "fn a() { test_macro!(b.c«()») }", + "fn a() { test_macro!(b.c(«vec»)) }", + "fn a() { test_macro!(b.c(vec«!»)) }", + "fn a() { test_macro!(b.c(vec!«[]»)) }", + "fn a() { test_macro!(b.c(vec![«d»])) }", + "fn a() { test_macro!(b.c(vec![d«.»])) }", + "fn a() { test_macro!(b.c(vec![d.«e»])) }", ], ); @@ -278,7 +278,7 @@ fn test_typing_multiple_new_injections() { &syntax_map, &buffer, &["field"], - "fn a() { dbg!(b.«c»(vec![d.«e»])) }", + "fn a() { test_macro!(b.«c»(vec![d.«e»])) }", ); } From 625c9d89802fa580ca1860975a6ddd53f13656ff Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 10:13:40 +0200 Subject: [PATCH 020/334] Remove some todo!'s --- crates/editor/src/test.rs | 6 +- crates/gpui/docs/key_dispatch.md | 4 +- crates/gpui/src/action.rs | 1 - crates/gpui/src/app/test_context.rs | 5 +- crates/gpui/src/elements/overlay.rs | 2 +- crates/gpui/src/platform/test/display.rs | 2 +- crates/gpui/src/platform/test/platform.rs | 1 - crates/gpui/tests/action_macros.rs | 12 +-- crates/workspace/src/dock.rs | 1 - crates/workspace/src/notifications.rs | 99 ----------------------- crates/workspace/src/pane_group.rs | 3 +- crates/workspace/src/toolbar.rs | 83 ------------------- crates/workspace/src/workspace.rs | 9 +-- crates/zed/src/zed.rs | 1 - 14 files changed, 18 insertions(+), 211 deletions(-) diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 4ce539ad797b2a3064a57ca15ff8e97b0f3c307e..d3337db2581b0bc7148059457cc9eef6943cc8d1 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -60,8 +60,7 @@ pub fn assert_text_with_selections( #[allow(dead_code)] #[cfg(any(test, feature = "test-support"))] pub(crate) fn build_editor(buffer: Model, cx: &mut ViewContext) -> Editor { - // todo!() - Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx) + Editor::new(EditorMode::Full, buffer, None, cx) } pub(crate) fn build_editor_with_project( @@ -69,6 +68,5 @@ pub(crate) fn build_editor_with_project( buffer: Model, cx: &mut ViewContext, ) -> Editor { - // todo!() - Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx) + Editor::new(EditorMode::Full, buffer, Some(project), cx) } diff --git a/crates/gpui/docs/key_dispatch.md b/crates/gpui/docs/key_dispatch.md index daf6f820cd5c98f45415c071d38518d55b269d2f..804a0b576194b2be4f23852a49369d988b9d81cb 100644 --- a/crates/gpui/docs/key_dispatch.md +++ b/crates/gpui/docs/key_dispatch.md @@ -50,7 +50,7 @@ impl Render for Menu { .on_action(|this, move: &MoveDown, cx| { // ... }) - .children(todo!()) + .children(unimplemented!()) } } ``` @@ -68,7 +68,7 @@ impl Render for Menu { .on_action(|this, move: &MoveDown, cx| { // ... }) - .children(todo!()) + .children(unimplemented!()) } } ``` diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index e335c4255e4deb0d6c5720b03ce017952a6fa229..81b392087aeb8f80571b7bb379948b1b886fe793 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -203,7 +203,6 @@ macro_rules! __impl_action { ) } - // todo!() why is this needed in addition to name? fn debug_name() -> &'static str where Self: ::std::marker::Sized diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 0f71ea61a9ec347b01e601f5e3c1a2237b80e691..a530aaf69b94ce65d35feee015738f306b3a79c3 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -467,12 +467,11 @@ impl View { } } - // todo!(start_waiting) - // cx.borrow().foreground_executor().start_waiting(); + cx.borrow().background_executor().start_waiting(); rx.recv() .await .expect("view dropped with pending condition"); - // cx.borrow().foreground_executor().finish_waiting(); + cx.borrow().background_executor().finish_waiting(); } }) .await diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index eab3ee60b41ded0f39fa548285b3d6be572e2ba1..6772baa2f9c117a834a716efe4d5e733948ce3b3 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -14,8 +14,8 @@ pub struct Overlay { children: SmallVec<[AnyElement; 2]>, anchor_corner: AnchorCorner, fit_mode: OverlayFitMode, - // todo!(); anchor_position: Option>, + // todo!(); // position_mode: OverlayPositionMode, } diff --git a/crates/gpui/src/platform/test/display.rs b/crates/gpui/src/platform/test/display.rs index 95f1daf8e92fc8bc620a91d3c1aa1ed12818c384..68dbb0fdf3466b9f3a2800bf68768aea2f5dd1e2 100644 --- a/crates/gpui/src/platform/test/display.rs +++ b/crates/gpui/src/platform/test/display.rs @@ -32,7 +32,7 @@ impl PlatformDisplay for TestDisplay { } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn bounds(&self) -> crate::Bounds { diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 695323e9c46b8e2a8f4260a682d8e214f58c43f4..ec3d7a0ff008ee64c92e8ceece0a2ef32cacff14 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -103,7 +103,6 @@ impl TestPlatform { } } -// todo!("implement out what our tests needed in GPUI 1") impl Platform for TestPlatform { fn background_executor(&self) -> BackgroundExecutor { self.background_executor.clone() diff --git a/crates/gpui/tests/action_macros.rs b/crates/gpui/tests/action_macros.rs index 9e5f6dea16ca6ad0ad7a1eb7f7098b61e0fd4cea..99572a4b3c5b1c84a4aa65fb95c72b25b2aef9ec 100644 --- a/crates/gpui/tests/action_macros.rs +++ b/crates/gpui/tests/action_macros.rs @@ -18,33 +18,33 @@ fn test_action_macros() { impl gpui::Action for RegisterableAction { fn boxed_clone(&self) -> Box { - todo!() + unimplemented!() } fn as_any(&self) -> &dyn std::any::Any { - todo!() + unimplemented!() } fn partial_eq(&self, _action: &dyn gpui::Action) -> bool { - todo!() + unimplemented!() } fn name(&self) -> &str { - todo!() + unimplemented!() } fn debug_name() -> &'static str where Self: Sized, { - todo!() + unimplemented!() } fn build(_value: serde_json::Value) -> anyhow::Result> where Self: Sized, { - todo!() + unimplemented!() } } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index c13a00b11c897b46ef5e2ac69ae10848c573ebf2..adc4fb5c80b2fd35af7ae32a620a550c977e469a 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -395,7 +395,6 @@ impl Dock { }) .ok(); } - // todo!() we do not use this event in the production code (even in zed1), remove it PanelEvent::Activate => { if let Some(ix) = this .panel_entries diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 394772b9c45b0a31bf36260389ee2e2fcc7ec717..d70de8c13dc7b45522952474506d609e1a75788b 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -247,105 +247,6 @@ pub mod simple_message_notification { })) } } - // todo!() - // impl View for MessageNotification { - // fn ui_name() -> &'static str { - // "MessageNotification" - // } - - // fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { - // let theme = theme::current(cx).clone(); - // let theme = &theme.simple_message_notification; - - // enum MessageNotificationTag {} - - // let click_message = self.click_message.clone(); - // let message = match &self.message { - // NotificationMessage::Text(text) => { - // Text::new(text.to_owned(), theme.message.text.clone()).into_any() - // } - // NotificationMessage::Element(e) => e(theme.message.text.clone(), cx), - // }; - // let on_click = self.on_click.clone(); - // let has_click_action = on_click.is_some(); - - // Flex::column() - // .with_child( - // Flex::row() - // .with_child( - // message - // .contained() - // .with_style(theme.message.container) - // .aligned() - // .top() - // .left() - // .flex(1., true), - // ) - // .with_child( - // MouseEventHandler::new::(0, cx, |state, _| { - // let style = theme.dismiss_button.style_for(state); - // Svg::new("icons/x.svg") - // .with_color(style.color) - // .constrained() - // .with_width(style.icon_width) - // .aligned() - // .contained() - // .with_style(style.container) - // .constrained() - // .with_width(style.button_width) - // .with_height(style.button_width) - // }) - // .with_padding(Padding::uniform(5.)) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.dismiss(&Default::default(), cx); - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .aligned() - // .constrained() - // .with_height(cx.font_cache().line_height(theme.message.text.font_size)) - // .aligned() - // .top() - // .flex_float(), - // ), - // ) - // .with_children({ - // click_message - // .map(|click_message| { - // MouseEventHandler::new::( - // 0, - // cx, - // |state, _| { - // let style = theme.action_message.style_for(state); - - // Flex::row() - // .with_child( - // Text::new(click_message, style.text.clone()) - // .contained() - // .with_style(style.container), - // ) - // .contained() - // }, - // ) - // .on_click(MouseButton::Left, move |_, this, cx| { - // if let Some(on_click) = on_click.as_ref() { - // on_click(cx); - // this.dismiss(&Default::default(), cx); - // } - // }) - // // Since we're not using a proper overlay, we have to capture these extra events - // .on_down(MouseButton::Left, |_, _, _| {}) - // .on_up(MouseButton::Left, |_, _, _| {}) - // .with_cursor_style(if has_click_action { - // CursorStyle::PointingHand - // } else { - // CursorStyle::Arrow - // }) - // }) - // .into_iter() - // }) - // .into_any() - // } - // } } pub trait NotifyResultExt { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 68fd3c084f591e8bd7401c66e056e38beada1278..e28d0e63deda2a97ac3c4a9cd77d4beebb802333 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use std::sync::Arc; use ui::{prelude::*, Button}; -const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4) +const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; @@ -707,7 +707,6 @@ mod element { proposed_current_pixel_change -= current_pixel_change; } - // todo!(schedule serialize) workspace .update(cx, |this, cx| this.schedule_serialize(cx)) .log_err(); diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index dc17cd3c1956b6f8475f04c6478ab86a86eb5689..cc072b08b9eac0cf13039dd45216483018d5701a 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -133,82 +133,6 @@ impl Render for Toolbar { } } -// todo!() -// impl View for Toolbar { -// fn ui_name() -> &'static str { -// "Toolbar" -// } - -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// let theme = &theme::current(cx).workspace.toolbar; - -// let mut primary_left_items = Vec::new(); -// let mut primary_right_items = Vec::new(); -// let mut secondary_item = None; -// let spacing = theme.item_spacing; -// let mut primary_items_row_count = 1; - -// for (item, position) in &self.items { -// match *position { -// ToolbarItemLocation::Hidden => {} - -// ToolbarItemLocation::PrimaryLeft { flex } => { -// primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); -// let left_item = ChildView::new(item.as_any(), cx).aligned(); -// if let Some((flex, expanded)) = flex { -// primary_left_items.push(left_item.flex(flex, expanded).into_any()); -// } else { -// primary_left_items.push(left_item.into_any()); -// } -// } - -// ToolbarItemLocation::PrimaryRight { flex } => { -// primary_items_row_count = primary_items_row_count.max(item.row_count(cx)); -// let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float(); -// if let Some((flex, expanded)) = flex { -// primary_right_items.push(right_item.flex(flex, expanded).into_any()); -// } else { -// primary_right_items.push(right_item.into_any()); -// } -// } - -// ToolbarItemLocation::Secondary => { -// secondary_item = Some( -// ChildView::new(item.as_any(), cx) -// .constrained() -// .with_height(theme.height * item.row_count(cx) as f32) -// .into_any(), -// ); -// } -// } -// } - -// let container_style = theme.container; -// let height = theme.height * primary_items_row_count as f32; - -// let mut primary_items = Flex::row().with_spacing(spacing); -// primary_items.extend(primary_left_items); -// primary_items.extend(primary_right_items); - -// let mut toolbar = Flex::column(); -// if !primary_items.is_empty() { -// toolbar.add_child(primary_items.constrained().with_height(height)); -// } -// if let Some(secondary_item) = secondary_item { -// toolbar.add_child(secondary_item); -// } - -// if toolbar.is_empty() { -// toolbar.into_any_named("toolbar") -// } else { -// toolbar -// .contained() -// .with_style(container_style) -// .into_any_named("toolbar") -// } -// } -// } - impl Toolbar { pub fn new() -> Self { Self { @@ -312,10 +236,3 @@ impl ToolbarItemViewHandle for View { self.read(cx).row_count(cx) } } - -// todo!() -// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle { -// fn from(val: &dyn ToolbarItemViewHandle) -> Self { -// val.as_any().clone() -// } -// } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ad02637ae39795c0e61ee38074062c518d12a3eb..739ce88636ca32a88be1496103ad03ed42f63808 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -943,10 +943,8 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { let to_load = if let Some(pane) = pane.upgrade() { - // todo!("focus") - // cx.focus(&pane); - pane.update(cx, |pane, cx| { + pane.focus(cx); loop { // Retrieve the weak item handle from the history. let entry = pane.nav_history_mut().pop(mode, cx)?; @@ -1631,8 +1629,7 @@ impl Workspace { }); } - // todo!("focus") - // cx.focus_self(); + cx.focus_self(); cx.notify(); self.serialize_workspace(cx); } @@ -1713,6 +1710,7 @@ impl Workspace { cx.notify(); } + // todo!() // #[cfg(any(test, feature = "test-support"))] // pub fn zoomed_view(&self, cx: &AppContext) -> Option { // self.zoomed.and_then(|view| view.upgrade(cx)) @@ -2992,7 +2990,6 @@ impl Workspace { cx.notify(); } - #[allow(unused)] fn schedule_serialize(&mut self, cx: &mut ViewContext) { self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move { cx.background_executor() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 73368a988377e8edb927fb1d629c78385112a492..0b90961d2352a31e80f38dbd3eda86df4055e788 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -764,7 +764,6 @@ fn open_bundled_file( .detach_and_log_err(cx); } -// todo!() #[cfg(test)] mod tests { use super::*; From 29ed067b2651c881a99c9c3be9f1d3e523911d0c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 12:20:52 +0100 Subject: [PATCH 021/334] Add a missing default value to docs (#3973) Release Notes: - N/A --- crates/call/src/call_settings.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index 88c3fe84ce118e011e729e335cf8631ff273e014..441323ad5ffcf4d6d525525139905278bd15ca0b 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -9,9 +9,12 @@ pub struct CallSettings { pub mute_on_join: bool, } +/// Configuration of voice calls in Zed. #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct CallSettingsContent { /// Whether the microphone should be muted when joining a channel or a call. + /// + /// Default: false pub mute_on_join: Option, } From f55870f378f41e8f9d093a4a2f683590cd9b75d1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 Jan 2024 12:37:24 +0100 Subject: [PATCH 022/334] Reuse mouse and keyboard listeners when reusing geometry for a view --- crates/gpui/src/key_dispatch.rs | 81 ++++++++++++++++++++--- crates/gpui/src/view.rs | 3 +- crates/gpui/src/window.rs | 113 ++++++++++++++++---------------- 3 files changed, 131 insertions(+), 66 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 22c4dffc03a78df8fde5530a3059887e91a2b876..9019670b04f4c66ea76761026a73a8fd6a0931dc 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -1,12 +1,13 @@ use crate::{ - arena::ArenaRef, Action, ActionRegistry, DispatchPhase, FocusId, KeyBinding, KeyContext, - KeyMatch, Keymap, Keystroke, KeystrokeMatcher, WindowContext, + Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, KeyMatch, + Keymap, Keystroke, KeystrokeMatcher, WindowContext, }; -use collections::HashMap; +use collections::FxHashMap; use parking_lot::Mutex; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, + mem, rc::Rc, sync::Arc, }; @@ -18,8 +19,9 @@ pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, nodes: Vec, - focusable_node_ids: HashMap, - keystroke_matchers: HashMap, KeystrokeMatcher>, + focusable_node_ids: FxHashMap, + view_node_ids: FxHashMap, + keystroke_matchers: FxHashMap, KeystrokeMatcher>, keymap: Arc>, action_registry: Rc, } @@ -30,15 +32,16 @@ pub(crate) struct DispatchNode { pub action_listeners: Vec, pub context: Option, focus_id: Option, + view_id: Option, parent: Option, } -type KeyListener = ArenaRef; +type KeyListener = Rc; #[derive(Clone)] pub(crate) struct DispatchActionListener { pub(crate) action_type: TypeId, - pub(crate) listener: ArenaRef, + pub(crate) listener: Rc, } impl DispatchTree { @@ -47,8 +50,9 @@ impl DispatchTree { node_stack: Vec::new(), context_stack: Vec::new(), nodes: Vec::new(), - focusable_node_ids: HashMap::default(), - keystroke_matchers: HashMap::default(), + focusable_node_ids: FxHashMap::default(), + view_node_ids: FxHashMap::default(), + keystroke_matchers: FxHashMap::default(), keymap, action_registry, } @@ -59,6 +63,7 @@ impl DispatchTree { self.nodes.clear(); self.context_stack.clear(); self.focusable_node_ids.clear(); + self.view_node_ids.clear(); self.keystroke_matchers.clear(); } @@ -83,6 +88,56 @@ impl DispatchTree { } } + fn move_node(&mut self, source_node: &mut DispatchNode) { + self.push_node(source_node.context.take()); + if let Some(focus_id) = source_node.focus_id { + self.make_focusable(focus_id); + } + if let Some(view_id) = source_node.view_id { + self.associate_view(view_id); + } + + let target_node = self.active_node(); + target_node.key_listeners = mem::take(&mut source_node.key_listeners); + target_node.action_listeners = mem::take(&mut source_node.action_listeners); + } + + pub fn graft(&mut self, view_id: EntityId, source: &mut Self) { + let view_source_node_id = source + .view_node_ids + .get(&view_id) + .expect("view should exist in previous dispatch tree"); + let view_source_node = &mut source.nodes[view_source_node_id.0]; + self.move_node(view_source_node); + + let mut source_stack = vec![*view_source_node_id]; + for (source_node_id, source_node) in source + .nodes + .iter_mut() + .enumerate() + .skip(view_source_node_id.0 + 1) + { + let source_node_id = DispatchNodeId(source_node_id); + while let Some(source_ancestor) = source_stack.last() { + if source_node.parent != Some(*source_ancestor) { + source_stack.pop(); + self.pop_node(); + } + } + + if source_stack.is_empty() { + break; + } else { + source_stack.push(source_node_id); + self.move_node(source_node); + } + } + + while !source_stack.is_empty() { + self.pop_node(); + } + } + pub fn clear_pending_keystrokes(&mut self) { self.keystroke_matchers.clear(); } @@ -117,7 +172,7 @@ impl DispatchTree { pub fn on_action( &mut self, action_type: TypeId, - listener: ArenaRef, + listener: Rc, ) { self.active_node() .action_listeners @@ -133,6 +188,12 @@ impl DispatchTree { self.focusable_node_ids.insert(focus_id, node_id); } + pub fn associate_view(&mut self, view_id: EntityId) { + let node_id = self.active_node_id(); + self.active_node().view_id = Some(view_id); + self.view_node_ids.insert(view_id, node_id); + } + pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { if parent == child { return true; diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 9d504627ab185212df7df14399dfc54bcf1d6b46..24bc00ce39796890158bc6890e0fb779801a73b3 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -285,7 +285,8 @@ impl Element for AnyView { && cache_key.text_style == cx.text_style() && !cx.window.dirty_views.contains(&self.entity_id()) { - println!("could reuse geometry for view {}", self.entity_id()); + cx.reuse_geometry(); + return; } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 98c81d3883122066edef4254d90b8adb6ba0ad3c..e5f9195b692409ec176d40d6014a22b5ceaaa6f8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,8 +1,8 @@ use crate::{ - px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef, - AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, - DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, - Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, + px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, + AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, + DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, + EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, @@ -95,7 +95,7 @@ impl DispatchPhase { } type AnyObserver = Box bool + 'static>; -type AnyMouseListener = ArenaBox; +type AnyMouseListener = Box; type AnyWindowFocusListener = Box bool + 'static>; struct FocusEvent { @@ -257,7 +257,6 @@ pub struct Window { pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, pub(crate) dirty_views: FxHashSet, - frame_arena: Arena, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, blur_listeners: SubscriberSet<(), AnyObserver>, @@ -288,7 +287,7 @@ pub(crate) struct ElementStateBox { pub(crate) struct Frame { focus: Option, pub(crate) element_states: FxHashMap, - mouse_listeners: FxHashMap>, + mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, pub(crate) scene_builder: SceneBuilder, pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, @@ -298,6 +297,7 @@ pub(crate) struct Frame { element_offset_stack: Vec>, pub(crate) view_parents: FxHashMap, pub(crate) view_stack: Vec, + pub(crate) reused_views: FxHashSet, } impl Frame { @@ -315,6 +315,7 @@ impl Frame { element_offset_stack: Vec::new(), view_parents: FxHashMap::default(), view_stack: Vec::new(), + reused_views: FxHashSet::default(), } } @@ -326,6 +327,7 @@ impl Frame { self.next_stacking_order_id = 0; self.view_parents.clear(); debug_assert!(self.view_stack.is_empty()); + self.reused_views.clear(); } fn focus_path(&self) -> SmallVec<[FocusId; 8]> { @@ -412,7 +414,6 @@ impl Window { rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())), dirty_views: FxHashSet::default(), - frame_arena: Arena::new(1024 * 1024), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), blur_listeners: SubscriberSet::new(), @@ -886,21 +887,21 @@ impl<'a> WindowContext<'a> { mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { let order = self.window.next_frame.z_index_stack.clone(); - let handler = self - .window - .frame_arena - .alloc(|| { - move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_>| { - handler(event.downcast_ref().unwrap(), phase, cx) - } - }) - .map(|handler| handler as _); + let view_id = *self.window.next_frame.view_stack.last().unwrap(); self.window .next_frame .mouse_listeners .entry(TypeId::of::()) .or_default() - .push((order, handler)) + .push(( + order, + view_id, + Box::new( + move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_>| { + handler(event.downcast_ref().unwrap(), phase, cx) + }, + ), + )) } /// Register a key event listener on the window for the next frame. The type of event @@ -913,21 +914,13 @@ impl<'a> WindowContext<'a> { &mut self, listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let listener = self - .window - .frame_arena - .alloc(|| { - move |event: &dyn Any, phase, cx: &mut WindowContext<'_>| { - if let Some(event) = event.downcast_ref::() { - listener(event, phase, cx) - } + self.window.next_frame.dispatch_tree.on_key_event(Rc::new( + move |event: &dyn Any, phase, cx: &mut WindowContext<'_>| { + if let Some(event) = event.downcast_ref::() { + listener(event, phase, cx) } - }) - .map(|handler| handler as _); - self.window - .next_frame - .dispatch_tree - .on_key_event(ArenaRef::from(listener)); + }, + )); } /// Register an action listener on the window for the next frame. The type of action @@ -941,15 +934,10 @@ impl<'a> WindowContext<'a> { action_type: TypeId, listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static, ) { - let listener = self - .window - .frame_arena - .alloc(|| listener) - .map(|handler| handler as _); self.window .next_frame .dispatch_tree - .on_action(action_type, ArenaRef::from(listener)); + .on_action(action_type, Rc::new(listener)); } pub fn is_action_available(&self, action: &dyn Action) -> bool { @@ -1327,6 +1315,16 @@ impl<'a> WindowContext<'a> { ); } + pub(crate) fn reuse_geometry(&mut self) { + let window = &mut self.window; + let view_id = *window.next_frame.view_stack.last().unwrap(); + assert!(window.next_frame.reused_views.insert(view_id)); + window + .next_frame + .dispatch_tree + .graft(view_id, &mut window.rendered_frame.dispatch_tree) + } + /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { println!("====================="); @@ -1342,26 +1340,18 @@ impl<'a> WindowContext<'a> { self.window.platform_window.clear_input_handler(); self.window.layout_engine.as_mut().unwrap().clear(); self.window.next_frame.clear(); - self.window.frame_arena.clear(); let root_view = self.window.root_view.take().unwrap(); self.with_z_index(0, |cx| { cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { for (action_type, action_listeners) in &cx.app.global_action_listeners { for action_listener in action_listeners.iter().cloned() { - let listener = cx - .window - .frame_arena - .alloc(|| { - move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| { - action_listener(action, phase, cx) - } - }) - .map(|listener| listener as _); - cx.window - .next_frame - .dispatch_tree - .on_action(*action_type, ArenaRef::from(listener)) + cx.window.next_frame.dispatch_tree.on_action( + *action_type, + Rc::new(move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| { + action_listener(action, phase, cx) + }), + ) } } @@ -1395,6 +1385,19 @@ impl<'a> WindowContext<'a> { ); self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); + for (type_id, listeners) in &mut self.window.rendered_frame.mouse_listeners { + let next_listeners = self + .window + .next_frame + .mouse_listeners + .entry(*type_id) + .or_default(); + for (order, view_id, listener) in listeners.drain(..) { + if self.window.next_frame.reused_views.contains(&view_id) { + next_listeners.push((order, view_id, listener)); + } + } + } let previous_focus_path = self.window.rendered_frame.focus_path(); mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); @@ -1540,11 +1543,11 @@ impl<'a> WindowContext<'a> { .remove(&event.type_id()) { // Because handlers may add other handlers, we sort every time. - handlers.sort_by(|(a, _), (b, _)| a.cmp(b)); + handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); // Capture phase, events bubble from back to front. Handlers for this phase are used for // special purposes, such as detecting events outside of a given Bounds. - for (_, handler) in &mut handlers { + for (_, _, handler) in &mut handlers { handler(event, DispatchPhase::Capture, self); if !self.app.propagate_event { break; @@ -1553,7 +1556,7 @@ impl<'a> WindowContext<'a> { // Bubble phase, where most normal handlers do their work. if self.app.propagate_event { - for (_, handler) in handlers.iter_mut().rev() { + for (_, _, handler) in handlers.iter_mut().rev() { handler(event, DispatchPhase::Bubble, self); if !self.app.propagate_event { break; From 84b05d6c0564948369383a953a65666597b082eb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 Jan 2024 15:12:23 +0100 Subject: [PATCH 023/334] Maintain view stack as part of `DispatchTree` --- crates/gpui/src/key_dispatch.rs | 42 +++++++++++++++-- crates/gpui/src/scene.rs | 79 +++++++++++++++++-------------- crates/gpui/src/window.rs | 83 +++++++++++++++++++++++---------- 3 files changed, 139 insertions(+), 65 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 9019670b04f4c66ea76761026a73a8fd6a0931dc..af836b7166635f85002a00b11b61a8643ab6b31c 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -4,7 +4,7 @@ use crate::{ }; use collections::FxHashMap; use parking_lot::Mutex; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use std::{ any::{Any, TypeId}, mem, @@ -18,6 +18,7 @@ pub struct DispatchNodeId(usize); pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, + view_stack: Vec, nodes: Vec, focusable_node_ids: FxHashMap, view_node_ids: FxHashMap, @@ -49,6 +50,7 @@ impl DispatchTree { Self { node_stack: Vec::new(), context_stack: Vec::new(), + view_stack: Vec::new(), nodes: Vec::new(), focusable_node_ids: FxHashMap::default(), view_node_ids: FxHashMap::default(), @@ -60,8 +62,9 @@ impl DispatchTree { pub fn clear(&mut self) { self.node_stack.clear(); - self.nodes.clear(); self.context_stack.clear(); + self.view_stack.clear(); + self.nodes.clear(); self.focusable_node_ids.clear(); self.view_node_ids.clear(); self.keystroke_matchers.clear(); @@ -82,10 +85,14 @@ impl DispatchTree { } pub fn pop_node(&mut self) { - let node_id = self.node_stack.pop().unwrap(); - if self.nodes[node_id.0].context.is_some() { + let node = &self.nodes[self.active_node_id().0]; + if node.context.is_some() { self.context_stack.pop(); } + if node.view_id.is_some() { + self.view_stack.pop(); + } + self.node_stack.pop(); } fn move_node(&mut self, source_node: &mut DispatchNode) { @@ -102,7 +109,7 @@ impl DispatchTree { target_node.action_listeners = mem::take(&mut source_node.action_listeners); } - pub fn graft(&mut self, view_id: EntityId, source: &mut Self) { + pub fn graft(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { let view_source_node_id = source .view_node_ids .get(&view_id) @@ -110,6 +117,7 @@ impl DispatchTree { let view_source_node = &mut source.nodes[view_source_node_id.0]; self.move_node(view_source_node); + let mut grafted_view_ids = smallvec![view_id]; let mut source_stack = vec![*view_source_node_id]; for (source_node_id, source_node) in source .nodes @@ -130,12 +138,17 @@ impl DispatchTree { } else { source_stack.push(source_node_id); self.move_node(source_node); + if let Some(view_id) = source_node.view_id { + grafted_view_ids.push(view_id); + } } } while !source_stack.is_empty() { self.pop_node(); } + + grafted_view_ids } pub fn clear_pending_keystrokes(&mut self) { @@ -192,6 +205,7 @@ impl DispatchTree { let node_id = self.active_node_id(); self.active_node().view_id = Some(view_id); self.view_node_ids.insert(view_id, node_id); + self.view_stack.push(view_id); } pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { @@ -322,6 +336,24 @@ impl DispatchTree { focus_path } + pub fn view_path(&self, view_id: EntityId) -> SmallVec<[EntityId; 8]> { + let mut view_path: SmallVec<[EntityId; 8]> = SmallVec::new(); + let mut current_node_id = self.view_node_ids.get(&view_id).copied(); + while let Some(node_id) = current_node_id { + let node = self.node(node_id); + if let Some(view_id) = node.view_id { + view_path.push(view_id); + } + current_node_id = node.parent; + } + view_path.reverse(); // Reverse the path so it goes from the root to the view node. + view_path + } + + pub fn active_view_id(&self) -> Option { + self.view_stack.last().copied() + } + pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode { &self.nodes[node_id.0] } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index e922c11f533680e102e67bfa7bd5d51b741678a3..42183100b7c63ae2c5959a2b885afb3ba626d219 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -11,13 +11,12 @@ pub(crate) type PointF = Point; pub(crate) type PathVertex_ScaledPixels = PathVertex; pub type LayerId = u32; - pub type DrawOrder = u32; #[derive(Default)] pub(crate) struct SceneBuilder { - last_order: Option<(StackingOrder, LayerId)>, layers_by_order: BTreeMap, + orders_by_layer: BTreeMap, shadows: Vec, quads: Vec, paths: Vec>, @@ -34,40 +33,39 @@ impl SceneBuilder { orders[*layer_id as usize] = ix as u32; } self.layers_by_order.clear(); - self.last_order = None; for shadow in &mut self.shadows { - shadow.order = orders[shadow.order as usize]; + shadow.order = orders[shadow.layer_id as usize]; } self.shadows.sort_by_key(|shadow| shadow.order); for quad in &mut self.quads { - quad.order = orders[quad.order as usize]; + quad.order = orders[quad.layer_id as usize]; } self.quads.sort_by_key(|quad| quad.order); for path in &mut self.paths { - path.order = orders[path.order as usize]; + path.order = orders[path.layer_id as usize]; } self.paths.sort_by_key(|path| path.order); for underline in &mut self.underlines { - underline.order = orders[underline.order as usize]; + underline.order = orders[underline.layer_id as usize]; } self.underlines.sort_by_key(|underline| underline.order); for monochrome_sprite in &mut self.monochrome_sprites { - monochrome_sprite.order = orders[monochrome_sprite.order as usize]; + monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize]; } self.monochrome_sprites.sort_by_key(|sprite| sprite.order); for polychrome_sprite in &mut self.polychrome_sprites { - polychrome_sprite.order = orders[polychrome_sprite.order as usize]; + polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize]; } self.polychrome_sprites.sort_by_key(|sprite| sprite.order); for surface in &mut self.surfaces { - surface.order = orders[surface.order as usize]; + surface.order = orders[surface.layer_id as usize]; } self.surfaces.sort_by_key(|surface| surface.order); @@ -96,53 +94,46 @@ impl SceneBuilder { let layer_id = self.layer_id_for_order(order); match primitive { Primitive::Shadow(mut shadow) => { - shadow.order = layer_id; + shadow.layer_id = layer_id; self.shadows.push(shadow); } Primitive::Quad(mut quad) => { - quad.order = layer_id; + quad.layer_id = layer_id; self.quads.push(quad); } Primitive::Path(mut path) => { - path.order = layer_id; + path.layer_id = layer_id; path.id = PathId(self.paths.len()); self.paths.push(path); } Primitive::Underline(mut underline) => { - underline.order = layer_id; + underline.layer_id = layer_id; self.underlines.push(underline); } Primitive::MonochromeSprite(mut sprite) => { - sprite.order = layer_id; + sprite.layer_id = layer_id; self.monochrome_sprites.push(sprite); } Primitive::PolychromeSprite(mut sprite) => { - sprite.order = layer_id; + sprite.layer_id = layer_id; self.polychrome_sprites.push(sprite); } Primitive::Surface(mut surface) => { - surface.order = layer_id; + surface.layer_id = layer_id; self.surfaces.push(surface); } } } - fn layer_id_for_order(&mut self, order: &StackingOrder) -> u32 { - if let Some((last_order, last_layer_id)) = self.last_order.as_ref() { - if last_order == order { - return *last_layer_id; - } - }; - - let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) { + fn layer_id_for_order(&mut self, order: &StackingOrder) -> LayerId { + if let Some(layer_id) = self.layers_by_order.get(order) { *layer_id } else { let next_id = self.layers_by_order.len() as LayerId; self.layers_by_order.insert(order.clone(), next_id); + self.orders_by_layer.insert(next_id, order.clone()); next_id - }; - self.last_order = Some((order.clone(), layer_id)); - layer_id + } } } @@ -439,7 +430,9 @@ pub(crate) enum PrimitiveBatch<'a> { #[derive(Default, Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Quad { - pub order: u32, // Initially a LayerId, then a DrawOrder. + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub background: Hsla, @@ -469,7 +462,9 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Underline { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub thickness: ScaledPixels, @@ -498,7 +493,9 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Shadow { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub corner_radii: Corners, pub content_mask: ContentMask, @@ -527,7 +524,9 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct MonochromeSprite { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub color: Hsla, @@ -558,7 +557,9 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct PolychromeSprite { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub corner_radii: Corners, @@ -589,7 +590,9 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Surface { - pub order: u32, + pub view_id: u32, + pub layer_id: LayerId, + pub order: DrawOrder, pub bounds: Bounds, pub content_mask: ContentMask, pub image_buffer: media::core_video::CVImageBuffer, @@ -619,7 +622,9 @@ pub(crate) struct PathId(pub(crate) usize); #[derive(Debug)] pub struct Path { pub(crate) id: PathId, - order: u32, + pub(crate) view_id: u32, + layer_id: LayerId, + order: DrawOrder, pub(crate) bounds: Bounds

, pub(crate) content_mask: ContentMask

, pub(crate) vertices: Vec>, @@ -633,6 +638,8 @@ impl Path { pub fn new(start: Point) -> Self { Self { id: PathId(0), + view_id: 0, + layer_id: 0, order: 0, vertices: Vec::new(), start, @@ -650,6 +657,8 @@ impl Path { pub fn scale(&self, factor: f32) -> Path { Path { id: self.id, + view_id: self.view_id, + layer_id: self.layer_id, order: self.order, bounds: self.bounds.scale(factor), content_mask: self.content_mask.scale(factor), diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index e5f9195b692409ec176d40d6014a22b5ceaaa6f8..f21b8b3b80d691f2707f4e2c18f2213809c40d36 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -295,8 +295,6 @@ pub(crate) struct Frame { pub(crate) next_stacking_order_id: u32, content_mask_stack: Vec>, element_offset_stack: Vec>, - pub(crate) view_parents: FxHashMap, - pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, } @@ -313,8 +311,6 @@ impl Frame { depth_map: Default::default(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), - view_parents: FxHashMap::default(), - view_stack: Vec::new(), reused_views: FxHashSet::default(), } } @@ -325,8 +321,6 @@ impl Frame { self.dispatch_tree.clear(); self.depth_map.clear(); self.next_stacking_order_id = 0; - self.view_parents.clear(); - debug_assert!(self.view_stack.is_empty()); self.reused_views.clear(); } @@ -886,8 +880,8 @@ impl<'a> WindowContext<'a> { &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { + let view_id = self.active_view_id(); let order = self.window.next_frame.z_index_stack.clone(); - let view_id = *self.window.next_frame.view_stack.last().unwrap(); self.window .next_frame .mouse_listeners @@ -1029,6 +1023,7 @@ impl<'a> WindowContext<'a> { ) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); + let view_id = self.active_view_id(); let window = &mut *self.window; for shadow in shadows { let mut shadow_bounds = bounds; @@ -1037,6 +1032,8 @@ impl<'a> WindowContext<'a> { window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, Shadow { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds: shadow_bounds.scale(scale_factor), content_mask: content_mask.scale(scale_factor), @@ -1054,11 +1051,14 @@ impl<'a> WindowContext<'a> { pub fn paint_quad(&mut self, quad: PaintQuad) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, Quad { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds: quad.bounds.scale(scale_factor), content_mask: content_mask.scale(scale_factor), @@ -1074,8 +1074,11 @@ impl<'a> WindowContext<'a> { pub fn paint_path(&mut self, mut path: Path, color: impl Into) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); + let view_id = self.active_view_id(); + path.content_mask = content_mask; path.color = color.into(); + path.view_id = view_id.as_u64() as u32; let window = &mut *self.window; window .next_frame @@ -1101,10 +1104,14 @@ impl<'a> WindowContext<'a> { size: size(width, height), }; let content_mask = self.content_mask(); + let view_id = self.active_view_id(); + let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, Underline { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds: bounds.scale(scale_factor), content_mask: content_mask.scale(scale_factor), @@ -1154,10 +1161,13 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, MonochromeSprite { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, content_mask, @@ -1204,11 +1214,14 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, PolychromeSprite { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, corner_radii: Default::default(), @@ -1246,11 +1259,14 @@ impl<'a> WindowContext<'a> { Ok((params.size, Cow::Owned(bytes))) })?; let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, MonochromeSprite { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, content_mask, @@ -1282,11 +1298,14 @@ impl<'a> WindowContext<'a> { })?; let content_mask = self.content_mask().scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, PolychromeSprite { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, content_mask, @@ -1303,10 +1322,13 @@ impl<'a> WindowContext<'a> { let scale_factor = self.scale_factor(); let bounds = bounds.scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor); + let view_id = self.active_view_id(); let window = &mut *self.window; window.next_frame.scene_builder.insert( &window.next_frame.z_index_stack, Surface { + view_id: view_id.as_u64() as u32, + layer_id: 0, order: 0, bounds, content_mask, @@ -1316,13 +1338,23 @@ impl<'a> WindowContext<'a> { } pub(crate) fn reuse_geometry(&mut self) { + let view_id = self.active_view_id(); let window = &mut self.window; - let view_id = *window.next_frame.view_stack.last().unwrap(); - assert!(window.next_frame.reused_views.insert(view_id)); - window + let grafted_view_ids = window .next_frame .dispatch_tree - .graft(view_id, &mut window.rendered_frame.dispatch_tree) + .graft(view_id, &mut window.rendered_frame.dispatch_tree); + for view_id in grafted_view_ids { + assert!(window.next_frame.reused_views.insert(view_id)); + } + } + + fn active_view_id(&self) -> EntityId { + self.window + .next_frame + .dispatch_tree + .active_view_id() + .expect("a view should always be active") } /// Draw pixels to the display for this window based on the contents of its scene. @@ -1375,6 +1407,7 @@ impl<'a> WindowContext<'a> { .draw(active_tooltip.cursor_offset, available_space, cx); }); } + self.window.dirty_views.clear(); self.window .next_frame @@ -1385,6 +1418,7 @@ impl<'a> WindowContext<'a> { ); self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); + for (type_id, listeners) in &mut self.window.rendered_frame.mouse_listeners { let next_listeners = self .window @@ -1434,7 +1468,6 @@ impl<'a> WindowContext<'a> { } self.window.drawing = false; - self.window.dirty_views.clear(); ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); scene @@ -2132,9 +2165,13 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { } fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - self.window_mut().next_frame.view_stack.push(view_id); + self.window_mut().next_frame.dispatch_tree.push_node(None); + self.window_mut() + .next_frame + .dispatch_tree + .associate_view(view_id); let result = f(self); - self.window_mut().next_frame.view_stack.pop(); + self.window_mut().next_frame.dispatch_tree.pop_node(); result } @@ -2495,17 +2532,13 @@ impl<'a, V: 'static> ViewContext<'a, V> { } pub fn notify(&mut self) { - let mut dirty_view_id = Some(self.view.entity_id()); - while let Some(view_id) = dirty_view_id { - if self.window_cx.window.dirty_views.insert(view_id) { - dirty_view_id = self - .window_cx - .window - .rendered_frame - .view_parents - .get(&view_id) - .copied(); - } else { + for view_id in self + .window + .rendered_frame + .dispatch_tree + .view_path(self.view.entity_id()) + { + if !self.window.dirty_views.insert(view_id) { break; } } From fa53353c575897b01e04eeeddb90d4e16729a700 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 10:11:20 -0500 Subject: [PATCH 024/334] Rename `IconElement` to just `Icon` (#3974) This PR renames the `IconElement` component to just `Icon`. This better matches the rest of our components, as `IconElement` was the only one using this naming convention. The `Icon` enum has been renamed to `IconName` to free up the name. I was trying to come up with a way that would allow rendering an `Icon::Zed` directly (and thus make the `IconElement` a hidden part of the API), but I couldn't come up with a way to do this cleanly. Release Notes: - N/A --- crates/assistant/src/assistant_panel.rs | 34 +-- crates/auto_update/src/update_notification.rs | 4 +- crates/collab_ui/src/chat_panel.rs | 14 +- crates/collab_ui/src/collab_panel.rs | 62 +++--- .../src/collab_panel/channel_modal.rs | 4 +- .../src/collab_panel/contact_finder.rs | 4 +- crates/collab_ui/src/collab_titlebar_item.rs | 20 +- crates/collab_ui/src/notification_panel.rs | 12 +- crates/copilot_ui/src/copilot_button.rs | 12 +- crates/copilot_ui/src/sign_in.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 12 +- crates/diagnostics/src/items.rs | 14 +- crates/diagnostics/src/toolbar_controls.rs | 4 +- crates/editor/src/editor.rs | 12 +- crates/feedback/src/deploy_feedback_button.rs | 4 +- crates/feedback/src/feedback_modal.rs | 2 +- crates/project_panel/src/project_panel.rs | 10 +- .../quick_action_bar/src/quick_action_bar.rs | 12 +- crates/search/src/buffer_search.rs | 16 +- crates/search/src/project_search.rs | 24 +-- crates/search/src/search.rs | 8 +- crates/search/src/search_bar.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 12 +- crates/terminal_view/src/terminal_view.rs | 4 +- crates/ui/src/components/button/button.rs | 10 +- .../ui/src/components/button/button_icon.rs | 12 +- .../ui/src/components/button/icon_button.rs | 10 +- crates/ui/src/components/checkbox.rs | 6 +- crates/ui/src/components/context_menu.rs | 10 +- crates/ui/src/components/disclosure.rs | 6 +- crates/ui/src/components/icon.rs | 202 +++++++++--------- crates/ui/src/components/keybinding.rs | 44 ++-- .../ui/src/components/list/list_sub_header.rs | 15 +- crates/ui/src/components/stories/button.rs | 6 +- crates/ui/src/components/stories/icon.rs | 8 +- .../ui/src/components/stories/icon_button.rs | 18 +- .../ui/src/components/stories/list_header.rs | 14 +- crates/ui/src/components/stories/list_item.rs | 8 +- crates/ui/src/components/stories/tab.rs | 2 +- crates/ui/src/components/stories/tab_bar.rs | 11 +- crates/ui/src/prelude.rs | 2 +- crates/workspace/src/dock.rs | 8 +- crates/workspace/src/notifications.rs | 4 +- crates/workspace/src/pane.rs | 18 +- crates/workspace/src/shared_screen.rs | 4 +- 45 files changed, 364 insertions(+), 360 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index f53343531af09083999e04f1d3dce92eafe34850..d4743afb714ab47be3e38b196a45174fb33c2aa5 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -933,7 +933,7 @@ impl AssistantPanel { } fn render_hamburger_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("hamburger_button", Icon::Menu) + IconButton::new("hamburger_button", IconName::Menu) .on_click(cx.listener(|this, _event, cx| { if this.active_editor().is_some() { this.set_active_editor_index(None, cx); @@ -957,7 +957,7 @@ impl AssistantPanel { } fn render_split_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("split_button", Icon::Snip) + IconButton::new("split_button", IconName::Snip) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx)); @@ -968,7 +968,7 @@ impl AssistantPanel { } fn render_assist_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("assist_button", Icon::MagicWand) + IconButton::new("assist_button", IconName::MagicWand) .on_click(cx.listener(|this, _event, cx| { if let Some(active_editor) = this.active_editor() { active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx)); @@ -979,7 +979,7 @@ impl AssistantPanel { } fn render_quote_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("quote_button", Icon::Quote) + IconButton::new("quote_button", IconName::Quote) .on_click(cx.listener(|this, _event, cx| { if let Some(workspace) = this.workspace.upgrade() { cx.window_context().defer(move |cx| { @@ -994,7 +994,7 @@ impl AssistantPanel { } fn render_plus_button(cx: &mut ViewContext) -> impl IntoElement { - IconButton::new("plus_button", Icon::Plus) + IconButton::new("plus_button", IconName::Plus) .on_click(cx.listener(|this, _event, cx| { this.new_conversation(cx); })) @@ -1004,12 +1004,12 @@ impl AssistantPanel { fn render_zoom_button(&self, cx: &mut ViewContext) -> impl IntoElement { let zoomed = self.zoomed; - IconButton::new("zoom_button", Icon::Maximize) + IconButton::new("zoom_button", IconName::Maximize) .on_click(cx.listener(|this, _event, cx| { this.toggle_zoom(&ToggleZoom, cx); })) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .icon_size(IconSize::Small) .tooltip(move |cx| { Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx) @@ -1286,8 +1286,8 @@ impl Panel for AssistantPanel { } } - fn icon(&self, cx: &WindowContext) -> Option { - Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button) + fn icon(&self, cx: &WindowContext) -> Option { + Some(IconName::Ai).filter(|_| AssistantSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -2349,7 +2349,7 @@ impl ConversationEditor { div() .id("error") .tooltip(move |cx| Tooltip::text(error.clone(), cx)) - .child(IconElement::new(Icon::XCircle)), + .child(Icon::new(IconName::XCircle)), ) } else { None @@ -2645,7 +2645,7 @@ impl Render for InlineAssistant { .justify_center() .w(measurements.gutter_width) .child( - IconButton::new("include_conversation", Icon::Ai) + IconButton::new("include_conversation", IconName::Ai) .on_click(cx.listener(|this, _, cx| { this.toggle_include_conversation(&ToggleIncludeConversation, cx) })) @@ -2660,7 +2660,7 @@ impl Render for InlineAssistant { ) .children(if SemanticIndex::enabled(cx) { Some( - IconButton::new("retrieve_context", Icon::MagnifyingGlass) + IconButton::new("retrieve_context", IconName::MagnifyingGlass) .on_click(cx.listener(|this, _, cx| { this.toggle_retrieve_context(&ToggleRetrieveContext, cx) })) @@ -2682,7 +2682,7 @@ impl Render for InlineAssistant { div() .id("error") .tooltip(move |cx| Tooltip::text(error_message.clone(), cx)) - .child(IconElement::new(Icon::XCircle).color(Color::Error)), + .child(Icon::new(IconName::XCircle).color(Color::Error)), ) } else { None @@ -2957,7 +2957,7 @@ impl InlineAssistant { div() .id("error") .tooltip(|cx| Tooltip::text("Not Authenticated. Please ensure you have a valid 'OPENAI_API_KEY' in your environment variables.", cx)) - .child(IconElement::new(Icon::XCircle)) + .child(Icon::new(IconName::XCircle)) .into_any_element() ), @@ -2965,7 +2965,7 @@ impl InlineAssistant { div() .id("error") .tooltip(|cx| Tooltip::text("Not Indexed", cx)) - .child(IconElement::new(Icon::XCircle)) + .child(Icon::new(IconName::XCircle)) .into_any_element() ), @@ -2996,7 +2996,7 @@ impl InlineAssistant { div() .id("update") .tooltip(move |cx| Tooltip::text(status_text.clone(), cx)) - .child(IconElement::new(Icon::Update).color(Color::Info)) + .child(Icon::new(IconName::Update).color(Color::Info)) .into_any_element() ) } @@ -3005,7 +3005,7 @@ impl InlineAssistant { div() .id("check") .tooltip(|cx| Tooltip::text("Index up to date", cx)) - .child(IconElement::new(Icon::Check).color(Color::Success)) + .child(Icon::new(IconName::Check).color(Color::Success)) .into_any_element() ), } diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index f00172591ecf37221e34cf4ff3b5b330b72fba28..65f786bca4c4e1a729974459fbdf8e549a9893cc 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -4,7 +4,7 @@ use gpui::{ }; use menu::Cancel; use util::channel::ReleaseChannel; -use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt}; +use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt}; pub struct UpdateNotification { version: SemanticVersion, @@ -30,7 +30,7 @@ impl Render for UpdateNotification { .child( div() .id("cancel") - .child(IconElement::new(Icon::Close)) + .child(Icon::new(IconName::Close)) .cursor_pointer() .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))), ), diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 4f8e12f1e8ea2be3ca2d5354a80e684e94269c22..5786ab10d4ca59b998b1b16ea7bb3c53611b4399 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; -use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip}; +use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -281,12 +281,12 @@ impl ChatPanel { )), ) .end_child( - IconButton::new("notes", Icon::File) + IconButton::new("notes", IconName::File) .on_click(cx.listener(Self::open_notes)) .tooltip(|cx| Tooltip::text("Open notes", cx)), ) .end_child( - IconButton::new("call", Icon::AudioOn) + IconButton::new("call", IconName::AudioOn) .on_click(cx.listener(Self::join_call)) .tooltip(|cx| Tooltip::text("Join call", cx)), ), @@ -395,7 +395,7 @@ impl ChatPanel { .w_8() .visible_on_hover("") .children(message_id_to_remove.map(|message_id| { - IconButton::new(("remove", message_id), Icon::XCircle).on_click( + IconButton::new(("remove", message_id), IconName::XCircle).on_click( cx.listener(move |this, _, cx| { this.remove_message(message_id, cx); }), @@ -429,7 +429,7 @@ impl ChatPanel { Button::new("sign-in", "Sign in") .style(ButtonStyle::Filled) .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .full_width() .on_click(cx.listener(move |this, _, cx| { @@ -622,12 +622,12 @@ impl Panel for ChatPanel { "ChatPanel" } - fn icon(&self, cx: &WindowContext) -> Option { + fn icon(&self, cx: &WindowContext) -> Option { if !is_channels_feature_enabled(cx) { return None; } - Some(ui::Icon::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) + Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 86d0131d70afa9dbf0a9c51d6943dcb1f1ad2f80..df8f2a251fdf4d86ff05f0eaa8052c34317435bb 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -31,7 +31,7 @@ use smallvec::SmallVec; use std::{mem, sync::Arc}; use theme::{ActiveTheme, ThemeSettings}; use ui::{ - prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, Label, + prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconName, IconSize, Label, ListHeader, ListItem, Tooltip, }; use util::{maybe, ResultExt, TryFutureExt}; @@ -848,7 +848,7 @@ impl CollabPanel { .end_slot(if is_pending { Label::new("Calling").color(Color::Muted).into_any_element() } else if is_current_user { - IconButton::new("leave-call", Icon::Exit) + IconButton::new("leave-call", IconName::Exit) .style(ButtonStyle::Subtle) .on_click(move |_, cx| Self::leave_call(cx)) .tooltip(|cx| Tooltip::text("Leave Call", cx)) @@ -897,7 +897,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(is_last, false, cx)) - .child(IconButton::new(0, Icon::Folder)), + .child(IconButton::new(0, IconName::Folder)), ) .child(Label::new(project_name.clone())) .tooltip(move |cx| Tooltip::text(format!("Open {}", project_name), cx)) @@ -918,7 +918,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(is_last, false, cx)) - .child(IconButton::new(0, Icon::Screen)), + .child(IconButton::new(0, IconName::Screen)), ) .child(Label::new("Screen")) .when_some(peer_id, |this, _| { @@ -959,7 +959,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(false, true, cx)) - .child(IconButton::new(0, Icon::File)), + .child(IconButton::new(0, IconName::File)), ) .child(div().h_7().w_full().child(Label::new("notes"))) .tooltip(move |cx| Tooltip::text("Open Channel Notes", cx)) @@ -980,7 +980,7 @@ impl CollabPanel { h_stack() .gap_1() .child(render_tree_branch(false, false, cx)) - .child(IconButton::new(0, Icon::MessageBubbles)), + .child(IconButton::new(0, IconName::MessageBubbles)), ) .child(Label::new("chat")) .tooltip(move |cx| Tooltip::text("Open Chat", cx)) @@ -1724,7 +1724,7 @@ impl CollabPanel { .child( Button::new("sign_in", "Sign in") .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .style(ButtonStyle::Filled) .full_width() @@ -1921,7 +1921,7 @@ impl CollabPanel { let button = match section { Section::ActiveCall => channel_link.map(|channel_link| { let channel_link_copy = channel_link.clone(); - IconButton::new("channel-link", Icon::Copy) + IconButton::new("channel-link", IconName::Copy) .icon_size(IconSize::Small) .size(ButtonSize::None) .visible_on_hover("section-header") @@ -1933,13 +1933,13 @@ impl CollabPanel { .into_any_element() }), Section::Contacts => Some( - IconButton::new("add-contact", Icon::Plus) + IconButton::new("add-contact", IconName::Plus) .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) .tooltip(|cx| Tooltip::text("Search for new contact", cx)) .into_any_element(), ), Section::Channels => Some( - IconButton::new("add-channel", Icon::Plus) + IconButton::new("add-channel", IconName::Plus) .on_click(cx.listener(|this, _, cx| this.new_root_channel(cx))) .tooltip(|cx| Tooltip::text("Create a channel", cx)) .into_any_element(), @@ -2010,7 +2010,7 @@ impl CollabPanel { }) .when(!calling, |el| { el.child( - IconButton::new("remove_contact", Icon::Close) + IconButton::new("remove_contact", IconName::Close) .icon_color(Color::Muted) .visible_on_hover("") .tooltip(|cx| Tooltip::text("Remove Contact", cx)) @@ -2071,13 +2071,13 @@ impl CollabPanel { let controls = if is_incoming { vec![ - IconButton::new("decline-contact", Icon::Close) + IconButton::new("decline-contact", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.respond_to_contact_request(user_id, false, cx); })) .icon_color(color) .tooltip(|cx| Tooltip::text("Decline invite", cx)), - IconButton::new("accept-contact", Icon::Check) + IconButton::new("accept-contact", IconName::Check) .on_click(cx.listener(move |this, _, cx| { this.respond_to_contact_request(user_id, true, cx); })) @@ -2086,7 +2086,7 @@ impl CollabPanel { ] } else { let github_login = github_login.clone(); - vec![IconButton::new("remove_contact", Icon::Close) + vec![IconButton::new("remove_contact", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.remove_contact(user_id, &github_login, cx); })) @@ -2126,13 +2126,13 @@ impl CollabPanel { }; let controls = [ - IconButton::new("reject-invite", Icon::Close) + IconButton::new("reject-invite", IconName::Close) .on_click(cx.listener(move |this, _, cx| { this.respond_to_channel_invite(channel_id, false, cx); })) .icon_color(color) .tooltip(|cx| Tooltip::text("Decline invite", cx)), - IconButton::new("accept-invite", Icon::Check) + IconButton::new("accept-invite", IconName::Check) .on_click(cx.listener(move |this, _, cx| { this.respond_to_channel_invite(channel_id, true, cx); })) @@ -2150,7 +2150,7 @@ impl CollabPanel { .child(h_stack().children(controls)), ) .start_slot( - IconElement::new(Icon::Hash) + Icon::new(IconName::Hash) .size(IconSize::Small) .color(Color::Muted), ) @@ -2162,7 +2162,7 @@ impl CollabPanel { cx: &mut ViewContext, ) -> ListItem { ListItem::new("contact-placeholder") - .child(IconElement::new(Icon::Plus)) + .child(Icon::new(IconName::Plus)) .child(Label::new("Add a Contact")) .selected(is_selected) .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) @@ -2246,7 +2246,7 @@ impl CollabPanel { }; let messages_button = |cx: &mut ViewContext| { - IconButton::new("channel_chat", Icon::MessageBubbles) + IconButton::new("channel_chat", IconName::MessageBubbles) .icon_size(IconSize::Small) .icon_color(if has_messages_notification { Color::Default @@ -2258,7 +2258,7 @@ impl CollabPanel { }; let notes_button = |cx: &mut ViewContext| { - IconButton::new("channel_notes", Icon::File) + IconButton::new("channel_notes", IconName::File) .icon_size(IconSize::Small) .icon_color(if has_notes_notification { Color::Default @@ -2315,9 +2315,13 @@ impl CollabPanel { }, )) .start_slot( - IconElement::new(if is_public { Icon::Public } else { Icon::Hash }) - .size(IconSize::Small) - .color(Color::Muted), + Icon::new(if is_public { + IconName::Public + } else { + IconName::Hash + }) + .size(IconSize::Small) + .color(Color::Muted), ) .child( h_stack() @@ -2386,7 +2390,7 @@ impl CollabPanel { .indent_level(depth + 1) .indent_step_size(px(20.)) .start_slot( - IconElement::new(Icon::Hash) + Icon::new(IconName::Hash) .size(IconSize::Small) .color(Color::Muted), ); @@ -2500,10 +2504,10 @@ impl Panel for CollabPanel { cx.notify(); } - fn icon(&self, cx: &gpui::WindowContext) -> Option { + fn icon(&self, cx: &gpui::WindowContext) -> Option { CollaborationPanelSettings::get_global(cx) .button - .then(|| ui::Icon::Collab) + .then(|| ui::IconName::Collab) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -2646,11 +2650,11 @@ impl Render for DraggedChannelView { .p_1() .gap_1() .child( - IconElement::new( + Icon::new( if self.channel.visibility == proto::ChannelVisibility::Public { - Icon::Public + IconName::Public } else { - Icon::Hash + IconName::Hash }, ) .size(IconSize::Small) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index f3ae16f7939910cef5d79581725003c7bfe1c987..8020613c1ae2a4cc2ac9e0ad61293541c451f0aa 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -168,7 +168,7 @@ impl Render for ChannelModal { .w_px() .flex_1() .gap_1() - .child(IconElement::new(Icon::Hash).size(IconSize::Medium)) + .child(Icon::new(IconName::Hash).size(IconSize::Medium)) .child(Label::new(channel_name)), ) .child( @@ -406,7 +406,7 @@ impl PickerDelegate for ChannelModalDelegate { Some(ChannelRole::Guest) => Some(Label::new("Guest")), _ => None, }) - .child(IconButton::new("ellipsis", Icon::Ellipsis)) + .child(IconButton::new("ellipsis", IconName::Ellipsis)) .children( if let (Some((menu, _)), true) = (&self.context_menu, selected) { Some( diff --git a/crates/collab_ui/src/collab_panel/contact_finder.rs b/crates/collab_ui/src/collab_panel/contact_finder.rs index dbcacef7d645de6e30959b7b4d0b91b2f41aceed..b769ec7e7f394fb7a94c38f6220b507d43e77ba1 100644 --- a/crates/collab_ui/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui/src/collab_panel/contact_finder.rs @@ -155,9 +155,7 @@ impl PickerDelegate for ContactFinderDelegate { .selected(selected) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) - .end_slot::( - icon_path.map(|icon_path| IconElement::from_path(icon_path)), - ), + .end_slot::(icon_path.map(|icon_path| Icon::from_path(icon_path))), ) } } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 6ccad2db0d107f4ee877ecdfd38563e880a79be5..f2106b9a8f4d769801d2eadc7c0259334966b8b6 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use theme::{ActiveTheme, PlayerColors}; use ui::{ h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, - IconButton, IconElement, TintColor, Tooltip, + IconButton, IconName, TintColor, Tooltip, }; use util::ResultExt; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; @@ -213,7 +213,7 @@ impl Render for CollabTitlebarItem { .child( div() .child( - IconButton::new("leave-call", ui::Icon::Exit) + IconButton::new("leave-call", ui::IconName::Exit) .style(ButtonStyle::Subtle) .tooltip(|cx| Tooltip::text("Leave call", cx)) .icon_size(IconSize::Small) @@ -230,9 +230,9 @@ impl Render for CollabTitlebarItem { IconButton::new( "mute-microphone", if is_muted { - ui::Icon::MicMute + ui::IconName::MicMute } else { - ui::Icon::Mic + ui::IconName::Mic }, ) .tooltip(move |cx| { @@ -256,9 +256,9 @@ impl Render for CollabTitlebarItem { IconButton::new( "mute-sound", if is_deafened { - ui::Icon::AudioOff + ui::IconName::AudioOff } else { - ui::Icon::AudioOn + ui::IconName::AudioOn }, ) .style(ButtonStyle::Subtle) @@ -281,7 +281,7 @@ impl Render for CollabTitlebarItem { ) .when(!read_only, |this| { this.child( - IconButton::new("screen-share", ui::Icon::Screen) + IconButton::new("screen-share", ui::IconName::Screen) .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) .selected(is_screen_sharing) @@ -573,7 +573,7 @@ impl CollabTitlebarItem { | client::Status::ReconnectionError { .. } => Some( div() .id("disconnected") - .child(IconElement::new(Icon::Disconnected).size(IconSize::Small)) + .child(Icon::new(IconName::Disconnected).size(IconSize::Small)) .tooltip(|cx| Tooltip::text("Disconnected", cx)) .into_any_element(), ), @@ -643,7 +643,7 @@ impl CollabTitlebarItem { h_stack() .gap_0p5() .child(Avatar::new(user.avatar_uri.clone())) - .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), ) .style(ButtonStyle::Subtle) .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), @@ -665,7 +665,7 @@ impl CollabTitlebarItem { .child( h_stack() .gap_0p5() - .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), ) .style(ButtonStyle::Subtle) .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index e7c94984b229165aa26a43000221446d7b56e7a5..95473044a3f4242cd497ce0087fe1e47e8865d6f 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::{sync::Arc, time::Duration}; use time::{OffsetDateTime, UtcOffset}; -use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconElement, Label}; +use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -553,7 +553,7 @@ impl Render for NotificationPanel { .border_b_1() .border_color(cx.theme().colors().border) .child(Label::new("Notifications")) - .child(IconElement::new(Icon::Envelope)), + .child(Icon::new(IconName::Envelope)), ) .map(|this| { if self.client.user_id().is_none() { @@ -564,7 +564,7 @@ impl Render for NotificationPanel { .child( Button::new("sign_in_prompt_button", "Sign in") .icon_color(Color::Muted) - .icon(Icon::Github) + .icon(IconName::Github) .icon_position(IconPosition::Start) .style(ButtonStyle::Filled) .full_width() @@ -655,10 +655,10 @@ impl Panel for NotificationPanel { } } - fn icon(&self, cx: &gpui::WindowContext) -> Option { + fn icon(&self, cx: &gpui::WindowContext) -> Option { (NotificationPanelSettings::get_global(cx).button && self.notification_store.read(cx).notification_count() > 0) - .then(|| Icon::Bell) + .then(|| IconName::Bell) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { @@ -716,7 +716,7 @@ impl Render for NotificationToast { .children(user.map(|user| Avatar::new(user.avatar_uri.clone()))) .child(Label::new(self.text.clone())) .child( - IconButton::new("close", Icon::Close) + IconButton::new("close", IconName::Close) .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))), ) .on_click(cx.listener(|this, _, cx| { diff --git a/crates/copilot_ui/src/copilot_button.rs b/crates/copilot_ui/src/copilot_button.rs index e55f45c29333edbecc22742fccf0d4cd10a4a0df..e5a1a942358a20c72fbb1037413796aeb84be77a 100644 --- a/crates/copilot_ui/src/copilot_button.rs +++ b/crates/copilot_ui/src/copilot_button.rs @@ -17,7 +17,9 @@ use util::{paths, ResultExt}; use workspace::{ create_and_open_local_file, item::ItemHandle, - ui::{popover_menu, ButtonCommon, Clickable, ContextMenu, Icon, IconButton, IconSize, Tooltip}, + ui::{ + popover_menu, ButtonCommon, Clickable, ContextMenu, IconButton, IconName, IconSize, Tooltip, + }, StatusItemView, Toast, Workspace, }; use zed_actions::OpenBrowser; @@ -51,15 +53,15 @@ impl Render for CopilotButton { .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None)); let icon = match status { - Status::Error(_) => Icon::CopilotError, + Status::Error(_) => IconName::CopilotError, Status::Authorized => { if enabled { - Icon::Copilot + IconName::Copilot } else { - Icon::CopilotDisabled + IconName::CopilotDisabled } } - _ => Icon::CopilotInit, + _ => IconName::CopilotInit, }; if let Status::Error(e) = status { diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index aeaa35784bfabe5ab75fc2b26a59dada83ddb61a..ba6f54b634a0e2f9f14ce296423fc905d40bf744 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -4,7 +4,7 @@ use gpui::{ FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Render, Styled, Subscription, ViewContext, }; -use ui::{prelude::*, Button, Icon, Label}; +use ui::{prelude::*, Button, IconName, Label}; use workspace::ModalView; const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot"; @@ -175,7 +175,7 @@ impl Render for CopilotCodeVerification { .w_32() .h_16() .flex_none() - .path(Icon::ZedXCopilot.path()) + .path(IconName::ZedXCopilot.path()) .text_color(cx.theme().colors().icon), ) .child(prompt) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 613fadf7f70caa6e35a7ecd739c8e16f6237be80..844a44c54f8bcf6eeae17ab3e629b4dee6e4a04b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -36,7 +36,7 @@ use std::{ }; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; -use ui::{h_stack, prelude::*, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, Icon, IconName, Label}; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, @@ -660,7 +660,7 @@ impl Item for ProjectDiagnosticsEditor { then.child( h_stack() .gap_1() - .child(IconElement::new(Icon::XCircle).color(Color::Error)) + .child(Icon::new(IconName::XCircle).color(Color::Error)) .child(Label::new(self.summary.error_count.to_string()).color( if selected { Color::Default @@ -674,9 +674,7 @@ impl Item for ProjectDiagnosticsEditor { then.child( h_stack() .gap_1() - .child( - IconElement::new(Icon::ExclamationTriangle).color(Color::Warning), - ) + .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning)) .child(Label::new(self.summary.warning_count.to_string()).color( if selected { Color::Default @@ -816,10 +814,10 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .flex_none() .map(|icon| { if diagnostic.severity == DiagnosticSeverity::ERROR { - icon.path(Icon::XCircle.path()) + icon.path(IconName::XCircle.path()) .text_color(Color::Error.color(cx)) } else { - icon.path(Icon::ExclamationTriangle.path()) + icon.path(IconName::ExclamationTriangle.path()) .text_color(Color::Warning.color(cx)) } }), diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 0c2d673d8e68b5bd43681788bde078442ed43d9d..035b84e1020048cd7c6d2cd107577b7c79786169 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -6,7 +6,7 @@ use gpui::{ }; use language::Diagnostic; use lsp::LanguageServerId; -use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconElement, Label, Tooltip}; +use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; use crate::{Deploy, ProjectDiagnosticsEditor}; @@ -25,7 +25,7 @@ impl Render for DiagnosticIndicator { let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { (0, 0) => h_stack().map(|this| { this.child( - IconElement::new(Icon::Check) + Icon::new(IconName::Check) .size(IconSize::Small) .color(Color::Default), ) @@ -33,7 +33,7 @@ impl Render for DiagnosticIndicator { (0, warning_count) => h_stack() .gap_1() .child( - IconElement::new(Icon::ExclamationTriangle) + Icon::new(IconName::ExclamationTriangle) .size(IconSize::Small) .color(Color::Warning), ) @@ -41,7 +41,7 @@ impl Render for DiagnosticIndicator { (error_count, 0) => h_stack() .gap_1() .child( - IconElement::new(Icon::XCircle) + Icon::new(IconName::XCircle) .size(IconSize::Small) .color(Color::Error), ) @@ -49,13 +49,13 @@ impl Render for DiagnosticIndicator { (error_count, warning_count) => h_stack() .gap_1() .child( - IconElement::new(Icon::XCircle) + Icon::new(IconName::XCircle) .size(IconSize::Small) .color(Color::Error), ) .child(Label::new(error_count.to_string()).size(LabelSize::Small)) .child( - IconElement::new(Icon::ExclamationTriangle) + Icon::new(IconName::ExclamationTriangle) .size(IconSize::Small) .color(Color::Warning), ) @@ -66,7 +66,7 @@ impl Render for DiagnosticIndicator { Some( h_stack() .gap_2() - .child(IconElement::new(Icon::ArrowCircle).size(IconSize::Small)) + .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small)) .child( Label::new("Checking…") .size(LabelSize::Small) diff --git a/crates/diagnostics/src/toolbar_controls.rs b/crates/diagnostics/src/toolbar_controls.rs index 897e2ccf40f573591d7b9e712e65928a17b28413..3c09e3fad91b952a2447cd78d30645edd3a9c44a 100644 --- a/crates/diagnostics/src/toolbar_controls.rs +++ b/crates/diagnostics/src/toolbar_controls.rs @@ -1,7 +1,7 @@ use crate::ProjectDiagnosticsEditor; use gpui::{div, EventEmitter, ParentElement, Render, ViewContext, WeakView}; use ui::prelude::*; -use ui::{Icon, IconButton, Tooltip}; +use ui::{IconButton, IconName, Tooltip}; use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub struct ToolbarControls { @@ -24,7 +24,7 @@ impl Render for ToolbarControls { }; div().child( - IconButton::new("toggle-warnings", Icon::ExclamationTriangle) + IconButton::new("toggle-warnings", IconName::ExclamationTriangle) .tooltip(move |cx| Tooltip::text(tooltip, cx)) .on_click(cx.listener(|this, _, cx| { if let Some(editor) = this.editor.as_ref().and_then(|editor| editor.upgrade()) { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 231f76218a44125e6c42f2a99f98027f98414ab1..9858cf8372eec5a57e4f1eeb419d06147d7ce51c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,8 +99,8 @@ use sum_tree::TreeMap; use text::{OffsetUtf16, Rope}; use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; use ui::{ - h_stack, prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, ListItem, Popover, - Tooltip, + h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, + Popover, Tooltip, }; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; @@ -4223,7 +4223,7 @@ impl Editor { ) -> Option { if self.available_code_actions.is_some() { Some( - IconButton::new("code_actions_indicator", ui::Icon::Bolt) + IconButton::new("code_actions_indicator", ui::IconName::Bolt) .icon_size(IconSize::Small) .icon_color(Color::Muted) .selected(is_active) @@ -4257,7 +4257,7 @@ impl Editor { fold_data .map(|(fold_status, buffer_row, active)| { (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| { - IconButton::new(ix as usize, ui::Icon::ChevronDown) + IconButton::new(ix as usize, ui::IconName::ChevronDown) .on_click(cx.listener(move |editor, _e, cx| match fold_status { FoldStatus::Folded => { editor.unfold_at(&UnfoldAt { buffer_row }, cx); @@ -4269,7 +4269,7 @@ impl Editor { .icon_color(ui::Color::Muted) .icon_size(ui::IconSize::Small) .selected(fold_status == FoldStatus::Folded) - .selected_icon(ui::Icon::ChevronRight) + .selected_icon(ui::IconName::ChevronRight) .size(ui::ButtonSize::None) }) }) @@ -9739,7 +9739,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren ), ) .child( - IconButton::new(("copy-block", cx.block_id), Icon::Copy) + IconButton::new(("copy-block", cx.block_id), IconName::Copy) .icon_color(Color::Muted) .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) diff --git a/crates/feedback/src/deploy_feedback_button.rs b/crates/feedback/src/deploy_feedback_button.rs index a02540bc5b3339e576f1a1eae336db119bd845ed..377d4cea5c11f3c3c7581e5a2d8b8034811f39e6 100644 --- a/crates/feedback/src/deploy_feedback_button.rs +++ b/crates/feedback/src/deploy_feedback_button.rs @@ -1,5 +1,5 @@ use gpui::{Render, ViewContext, WeakView}; -use ui::{prelude::*, ButtonCommon, Icon, IconButton, Tooltip}; +use ui::{prelude::*, ButtonCommon, IconButton, IconName, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::{feedback_modal::FeedbackModal, GiveFeedback}; @@ -27,7 +27,7 @@ impl Render for DeployFeedbackButton { }) }) .is_some(); - IconButton::new("give-feedback", Icon::Envelope) + IconButton::new("give-feedback", IconName::Envelope) .style(ui::ButtonStyle::Subtle) .icon_size(IconSize::Small) .selected(is_open) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index b197d602338e052b319623e9b1ad6a1e6f7d7b53..bf7a0715604f20ec6eae1d28ea4988016a8cd2cf 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -488,7 +488,7 @@ impl Render for FeedbackModal { .child( Button::new("community_repository", "Community Repository") .style(ButtonStyle::Transparent) - .icon(Icon::ExternalLink) + .icon(IconName::ExternalLink) .icon_position(IconPosition::End) .icon_size(IconSize::Small) .on_click(open_community_repo), diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 727ab7e859d237c8729401e6dcee90f8529616c8..251e26ebfba004b81a49c1ce28956e01f42bbce5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -30,7 +30,7 @@ use std::{ sync::Arc, }; use theme::ThemeSettings; -use ui::{prelude::*, v_stack, ContextMenu, IconElement, KeyBinding, Label, ListItem}; +use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1403,7 +1403,7 @@ impl ProjectPanel { .indent_step_size(px(settings.indent_size)) .selected(is_selected) .child(if let Some(icon) = &icon { - div().child(IconElement::from_path(icon.to_string()).color(Color::Muted)) + div().child(Icon::from_path(icon.to_string()).color(Color::Muted)) } else { div().size(IconSize::default().rems()).invisible() }) @@ -1590,7 +1590,7 @@ impl Render for DraggedProjectEntryView { .indent_level(self.details.depth) .indent_step_size(px(settings.indent_size)) .child(if let Some(icon) = &self.details.icon { - div().child(IconElement::from_path(icon.to_string())) + div().child(Icon::from_path(icon.to_string())) } else { div() }) @@ -1640,8 +1640,8 @@ impl Panel for ProjectPanel { cx.notify(); } - fn icon(&self, _: &WindowContext) -> Option { - Some(ui::Icon::FileTree) + fn icon(&self, _: &WindowContext) -> Option { + Some(ui::IconName::FileTree) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index b40794c2fad4cfe4d02be825e1ec49be82e29ff4..cf4941bcec66cdef77f3f4453a6b3eb25d8b1321 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -6,7 +6,7 @@ use gpui::{ Subscription, View, ViewContext, WeakView, }; use search::{buffer_search, BufferSearchBar}; -use ui::{prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, Tooltip}; +use ui::{prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, Tooltip}; use workspace::{ item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, }; @@ -43,7 +43,7 @@ impl Render for QuickActionBar { let inlay_hints_button = Some(QuickActionBarButton::new( "toggle inlay hints", - Icon::InlayHint, + IconName::InlayHint, editor.read(cx).inlay_hints_enabled(), Box::new(editor::ToggleInlayHints), "Toggle Inlay Hints", @@ -60,7 +60,7 @@ impl Render for QuickActionBar { let search_button = Some(QuickActionBarButton::new( "toggle buffer search", - Icon::MagnifyingGlass, + IconName::MagnifyingGlass, !self.buffer_search_bar.read(cx).is_dismissed(), Box::new(buffer_search::Deploy { focus: false }), "Buffer Search", @@ -77,7 +77,7 @@ impl Render for QuickActionBar { let assistant_button = QuickActionBarButton::new( "toggle inline assistant", - Icon::MagicWand, + IconName::MagicWand, false, Box::new(InlineAssist), "Inline Assist", @@ -107,7 +107,7 @@ impl EventEmitter for QuickActionBar {} #[derive(IntoElement)] struct QuickActionBarButton { id: ElementId, - icon: Icon, + icon: IconName, toggled: bool, action: Box, tooltip: SharedString, @@ -117,7 +117,7 @@ struct QuickActionBarButton { impl QuickActionBarButton { fn new( id: impl Into, - icon: Icon, + icon: IconName, toggled: bool, action: Box, tooltip: impl Into, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index c889f0a4a4c11d3f104e130e34e5b87c092565d6..9b2719936080fd14ba65c8f0b044c11455815f2c 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -21,7 +21,7 @@ use settings::Settings; use std::{any::Any, sync::Arc}; use theme::ThemeSettings; -use ui::{h_stack, prelude::*, Icon, IconButton, IconElement, ToggleButton, Tooltip}; +use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip}; use util::ResultExt; use workspace::{ item::ItemHandle, @@ -225,7 +225,7 @@ impl Render for BufferSearchBar { .border_color(editor_border) .min_w(rems(384. / 16.)) .rounded_lg() - .child(IconElement::new(Icon::MagnifyingGlass)) + .child(Icon::new(IconName::MagnifyingGlass)) .child(self.render_text_input(&self.query_editor, cx)) .children(supported_options.case.then(|| { self.render_search_option_button( @@ -287,7 +287,7 @@ impl Render for BufferSearchBar { this.child( IconButton::new( "buffer-search-bar-toggle-replace-button", - Icon::Replace, + IconName::Replace, ) .style(ButtonStyle::Subtle) .when(self.replace_enabled, |button| { @@ -323,7 +323,7 @@ impl Render for BufferSearchBar { ) .when(should_show_replace_input, |this| { this.child( - IconButton::new("search-replace-next", ui::Icon::ReplaceNext) + IconButton::new("search-replace-next", ui::IconName::ReplaceNext) .tooltip(move |cx| { Tooltip::for_action("Replace next", &ReplaceNext, cx) }) @@ -332,7 +332,7 @@ impl Render for BufferSearchBar { })), ) .child( - IconButton::new("search-replace-all", ui::Icon::ReplaceAll) + IconButton::new("search-replace-all", ui::IconName::ReplaceAll) .tooltip(move |cx| { Tooltip::for_action("Replace all", &ReplaceAll, cx) }) @@ -350,7 +350,7 @@ impl Render for BufferSearchBar { .gap_0p5() .flex_none() .child( - IconButton::new("select-all", ui::Icon::SelectAll) + IconButton::new("select-all", ui::IconName::SelectAll) .on_click(|_, cx| cx.dispatch_action(SelectAllMatches.boxed_clone())) .tooltip(|cx| { Tooltip::for_action("Select all matches", &SelectAllMatches, cx) @@ -358,13 +358,13 @@ impl Render for BufferSearchBar { ) .children(match_count) .child(render_nav_button( - ui::Icon::ChevronLeft, + ui::IconName::ChevronLeft, self.active_match_index.is_some(), "Select previous match", &SelectPrevMatch, )) .child(render_nav_button( - ui::Icon::ChevronRight, + ui::IconName::ChevronRight, self.active_match_index.is_some(), "Select next match", &SelectNextMatch, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a370cca9f69c2e89b866232ccb2c385d7726ce25..5cdf614c1b9f8e22c416866db3a863e2458251c2 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -38,7 +38,7 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize, + h_stack, prelude::*, v_stack, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, Selectable, ToggleButton, Tooltip, }; use util::{paths::PathMatcher, ResultExt as _}; @@ -432,7 +432,7 @@ impl Item for ProjectSearchView { .unwrap_or_else(|| "Project search".into()); h_stack() .gap_2() - .child(IconElement::new(Icon::MagnifyingGlass).color(if selected { + .child(Icon::new(IconName::MagnifyingGlass).color(if selected { Color::Default } else { Color::Muted @@ -1616,12 +1616,12 @@ impl Render for ProjectSearchBar { .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) - .child(IconElement::new(Icon::MagnifyingGlass)) + .child(Icon::new(IconName::MagnifyingGlass)) .child(self.render_text_input(&search.query_editor, cx)) .child( h_stack() .child( - IconButton::new("project-search-filter-button", Icon::Filter) + IconButton::new("project-search-filter-button", IconName::Filter) .tooltip(|cx| { Tooltip::for_action("Toggle filters", &ToggleFilters, cx) }) @@ -1639,7 +1639,7 @@ impl Render for ProjectSearchBar { this.child( IconButton::new( "project-search-case-sensitive", - Icon::CaseSensitive, + IconName::CaseSensitive, ) .tooltip(|cx| { Tooltip::for_action( @@ -1659,7 +1659,7 @@ impl Render for ProjectSearchBar { )), ) .child( - IconButton::new("project-search-whole-word", Icon::WholeWord) + IconButton::new("project-search-whole-word", IconName::WholeWord) .tooltip(|cx| { Tooltip::for_action( "Toggle whole word", @@ -1738,7 +1738,7 @@ impl Render for ProjectSearchBar { }), ) .child( - IconButton::new("project-search-toggle-replace", Icon::Replace) + IconButton::new("project-search-toggle-replace", IconName::Replace) .on_click(cx.listener(|this, _, cx| { this.toggle_replace(&ToggleReplace, cx); })) @@ -1755,7 +1755,7 @@ impl Render for ProjectSearchBar { .border_1() .border_color(cx.theme().colors().border) .rounded_lg() - .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small)) + .child(Icon::new(IconName::Replace).size(ui::IconSize::Small)) .child(self.render_text_input(&search.replacement_editor, cx)) } else { // Fill out the space if we don't have a replacement editor. @@ -1764,7 +1764,7 @@ impl Render for ProjectSearchBar { let actions_column = h_stack() .when(search.replace_enabled, |this| { this.child( - IconButton::new("project-search-replace-next", Icon::ReplaceNext) + IconButton::new("project-search-replace-next", IconName::ReplaceNext) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { search.update(cx, |this, cx| { @@ -1775,7 +1775,7 @@ impl Render for ProjectSearchBar { .tooltip(|cx| Tooltip::for_action("Replace next match", &ReplaceNext, cx)), ) .child( - IconButton::new("project-search-replace-all", Icon::ReplaceAll) + IconButton::new("project-search-replace-all", IconName::ReplaceAll) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { search.update(cx, |this, cx| { @@ -1796,7 +1796,7 @@ impl Render for ProjectSearchBar { this }) .child( - IconButton::new("project-search-prev-match", Icon::ChevronLeft) + IconButton::new("project-search-prev-match", IconName::ChevronLeft) .disabled(search.active_match_index.is_none()) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { @@ -1810,7 +1810,7 @@ impl Render for ProjectSearchBar { }), ) .child( - IconButton::new("project-search-next-match", Icon::ChevronRight) + IconButton::new("project-search-next-match", IconName::ChevronRight) .disabled(search.active_match_index.is_none()) .on_click(cx.listener(|this, _, cx| { if let Some(search) = this.active_project_search.as_ref() { diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index f0301a5bcc7a637c17d47c59f5102f352ac3840c..1b29801e03a8b370411be0d1def77c392cccb1dd 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -60,11 +60,11 @@ impl SearchOptions { } } - pub fn icon(&self) -> ui::Icon { + pub fn icon(&self) -> ui::IconName { match *self { - SearchOptions::WHOLE_WORD => ui::Icon::WholeWord, - SearchOptions::CASE_SENSITIVE => ui::Icon::CaseSensitive, - SearchOptions::INCLUDE_IGNORED => ui::Icon::FileGit, + SearchOptions::WHOLE_WORD => ui::IconName::WholeWord, + SearchOptions::CASE_SENSITIVE => ui::IconName::CaseSensitive, + SearchOptions::INCLUDE_IGNORED => ui::IconName::FileGit, _ => panic!("{:?} is not a named SearchOption", self), } } diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 628be3112ecc06e9d00e8d33361e8f17fa6efd54..0594036c25483c8dadcbfdf5988edbc1ba12a65f 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -3,7 +3,7 @@ use ui::IconButton; use ui::{prelude::*, Tooltip}; pub(super) fn render_nav_button( - icon: ui::Icon, + icon: ui::IconName, active: bool, tooltip: &'static str, action: &'static dyn Action, diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 99929535700e2badbe0c447ac6dfc4ee5998e7e2..d0b52f5eb217ed60cc9b62e20657e5033506f133 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -19,7 +19,7 @@ use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::Item, pane, - ui::Icon, + ui::IconName, DraggedTab, Pane, Workspace, }; @@ -71,7 +71,7 @@ impl TerminalPanel { h_stack() .gap_2() .child( - IconButton::new("plus", Icon::Plus) + IconButton::new("plus", IconName::Plus) .icon_size(IconSize::Small) .on_click(move |_, cx| { terminal_panel @@ -82,10 +82,10 @@ impl TerminalPanel { ) .child({ let zoomed = pane.is_zoomed(); - IconButton::new("toggle_zoom", Icon::Maximize) + IconButton::new("toggle_zoom", IconName::Maximize) .icon_size(IconSize::Small) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .on_click(cx.listener(|pane, _, cx| { pane.toggle_zoom(&workspace::ToggleZoom, cx); })) @@ -477,8 +477,8 @@ impl Panel for TerminalPanel { "TerminalPanel" } - fn icon(&self, _cx: &WindowContext) -> Option { - Some(Icon::Terminal) + fn icon(&self, _cx: &WindowContext) -> Option { + Some(IconName::Terminal) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index f4fb6105cb8bcf476a12d68d29aadb0d942a22dc..4d2e78f0daddacb0bc33673b8a14c29f54d3fdb4 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -20,7 +20,7 @@ use terminal::{ Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal, }; use terminal_element::TerminalElement; -use ui::{h_stack, prelude::*, ContextMenu, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label}; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, @@ -690,7 +690,7 @@ impl Item for TerminalView { let title = self.terminal().read(cx).title(true); h_stack() .gap_2() - .child(IconElement::new(Icon::Terminal)) + .child(Icon::new(IconName::Terminal)) .child(Label::new(title).color(if selected { Color::Default } else { diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 1e60aae03b11e84eda1a2041565c6b75a5fae79f..398f8f10e27831a07bdca21f837e1425f396c5e9 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -2,7 +2,7 @@ use gpui::{AnyView, DefiniteLength}; use crate::{prelude::*, IconPosition, KeyBinding}; use crate::{ - ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle, + ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle, }; use super::button_icon::ButtonIcon; @@ -14,11 +14,11 @@ pub struct Button { label_color: Option, label_size: Option, selected_label: Option, - icon: Option, + icon: Option, icon_position: Option, icon_size: Option, icon_color: Option, - selected_icon: Option, + selected_icon: Option, key_binding: Option, } @@ -54,7 +54,7 @@ impl Button { self } - pub fn icon(mut self, icon: impl Into>) -> Self { + pub fn icon(mut self, icon: impl Into>) -> Self { self.icon = icon.into(); self } @@ -74,7 +74,7 @@ impl Button { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } diff --git a/crates/ui/src/components/button/button_icon.rs b/crates/ui/src/components/button/button_icon.rs index 15538bb24d79b9f17c3fcef8c4de37466e84fc66..b8f5427d30aaa6dfb21b802bdd49922de9e17433 100644 --- a/crates/ui/src/components/button/button_icon.rs +++ b/crates/ui/src/components/button/button_icon.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, Icon, IconElement, IconSize}; +use crate::{prelude::*, Icon, IconName, IconSize}; /// An icon that appears within a button. /// @@ -6,17 +6,17 @@ use crate::{prelude::*, Icon, IconElement, IconSize}; /// or as a standalone icon, like in [`IconButton`](crate::IconButton). #[derive(IntoElement)] pub(super) struct ButtonIcon { - icon: Icon, + icon: IconName, size: IconSize, color: Color, disabled: bool, selected: bool, - selected_icon: Option, + selected_icon: Option, selected_style: Option, } impl ButtonIcon { - pub fn new(icon: Icon) -> Self { + pub fn new(icon: IconName) -> Self { Self { icon, size: IconSize::default(), @@ -44,7 +44,7 @@ impl ButtonIcon { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } @@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon { self.color }; - IconElement::new(icon).size(self.size).color(icon_color) + Icon::new(icon).size(self.size).color(icon_color) } } diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index d9ed6ccb5d86a2c5122ef6cd2fd364be235566bc..7c5313497c77ff6532838966001d2cbd4a3688e0 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -1,21 +1,21 @@ use gpui::{AnyView, DefiniteLength}; use crate::{prelude::*, SelectableButton}; -use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize}; +use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize}; use super::button_icon::ButtonIcon; #[derive(IntoElement)] pub struct IconButton { base: ButtonLike, - icon: Icon, + icon: IconName, icon_size: IconSize, icon_color: Color, - selected_icon: Option, + selected_icon: Option, } impl IconButton { - pub fn new(id: impl Into, icon: Icon) -> Self { + pub fn new(id: impl Into, icon: IconName) -> Self { Self { base: ButtonLike::new(id), icon, @@ -35,7 +35,7 @@ impl IconButton { self } - pub fn selected_icon(mut self, icon: impl Into>) -> Self { + pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 3b778420291980ff70e3e8cebaa5b3c4d411033f..08c95f2d939f0fc76be90d2186fab9a6788c8f8f 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -1,7 +1,7 @@ use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext}; use crate::prelude::*; -use crate::{Color, Icon, IconElement, Selection}; +use crate::{Color, Icon, IconName, Selection}; pub type CheckHandler = Box; @@ -47,7 +47,7 @@ impl RenderOnce for Checkbox { let group_id = format!("checkbox_group_{:?}", self.id); let icon = match self.checked { - Selection::Selected => Some(IconElement::new(Icon::Check).size(IconSize::Small).color( + Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color( if self.disabled { Color::Disabled } else { @@ -55,7 +55,7 @@ impl RenderOnce for Checkbox { }, )), Selection::Indeterminate => Some( - IconElement::new(Icon::Dash) + Icon::new(IconName::Dash) .size(IconSize::Small) .color(if self.disabled { Color::Disabled diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 8666ec65651d6f00cbe225f7b065521673326037..098c54f33cb98d831c0806a2b4ec9f5b0a18ad07 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -1,6 +1,6 @@ use crate::{ - h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem, - ListSeparator, ListSubHeader, + h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator, + ListSubHeader, }; use gpui::{ px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, @@ -14,7 +14,7 @@ enum ContextMenuItem { Header(SharedString), Entry { label: SharedString, - icon: Option, + icon: Option, handler: Rc, action: Option>, }, @@ -117,7 +117,7 @@ impl ContextMenu { label: label.into(), action: Some(action.boxed_clone()), handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())), - icon: Some(Icon::Link), + icon: Some(IconName::Link), }); self } @@ -280,7 +280,7 @@ impl Render for ContextMenu { h_stack() .gap_1() .child(Label::new(label.clone())) - .child(IconElement::new(*icon)) + .child(Icon::new(*icon)) .into_any_element() } else { Label::new(label.clone()).into_any_element() diff --git a/crates/ui/src/components/disclosure.rs b/crates/ui/src/components/disclosure.rs index d4349f61a0f53b7f70af3255576aaa4c715796ae..59651ddb0b5ec9154c0180ca89e2331007cd3404 100644 --- a/crates/ui/src/components/disclosure.rs +++ b/crates/ui/src/components/disclosure.rs @@ -1,6 +1,6 @@ use gpui::ClickEvent; -use crate::{prelude::*, Color, Icon, IconButton, IconSize}; +use crate::{prelude::*, Color, IconButton, IconName, IconSize}; #[derive(IntoElement)] pub struct Disclosure { @@ -32,8 +32,8 @@ impl RenderOnce for Disclosure { IconButton::new( self.id, match self.is_open { - true => Icon::ChevronDown, - false => Icon::ChevronRight, + true => IconName::ChevronDown, + false => IconName::ChevronRight, }, ) .icon_color(Color::Muted) diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 4c6e48c0fc034dbcfbd4454c920b2d4a994b92b0..908e76ef918b56aefff6949e86ea6473a272253d 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -22,7 +22,7 @@ impl IconSize { } #[derive(Debug, PartialEq, Copy, Clone, EnumIter)] -pub enum Icon { +pub enum IconName { Ai, ArrowDown, ArrowLeft, @@ -111,118 +111,108 @@ pub enum Icon { ZedXCopilot, } -impl Icon { +impl IconName { pub fn path(self) -> &'static str { match self { - Icon::Ai => "icons/ai.svg", - Icon::ArrowDown => "icons/arrow_down.svg", - Icon::ArrowLeft => "icons/arrow_left.svg", - Icon::ArrowRight => "icons/arrow_right.svg", - Icon::ArrowUp => "icons/arrow_up.svg", - Icon::ArrowUpRight => "icons/arrow_up_right.svg", - Icon::ArrowCircle => "icons/arrow_circle.svg", - Icon::AtSign => "icons/at_sign.svg", - Icon::AudioOff => "icons/speaker_off.svg", - Icon::AudioOn => "icons/speaker_loud.svg", - Icon::Backspace => "icons/backspace.svg", - Icon::Bell => "icons/bell.svg", - Icon::BellOff => "icons/bell_off.svg", - Icon::BellRing => "icons/bell_ring.svg", - Icon::Bolt => "icons/bolt.svg", - Icon::CaseSensitive => "icons/case_insensitive.svg", - Icon::Check => "icons/check.svg", - Icon::ChevronDown => "icons/chevron_down.svg", - Icon::ChevronLeft => "icons/chevron_left.svg", - Icon::ChevronRight => "icons/chevron_right.svg", - Icon::ChevronUp => "icons/chevron_up.svg", - Icon::Close => "icons/x.svg", - Icon::Collab => "icons/user_group_16.svg", - Icon::Command => "icons/command.svg", - Icon::Control => "icons/control.svg", - Icon::Copilot => "icons/copilot.svg", - Icon::CopilotDisabled => "icons/copilot_disabled.svg", - Icon::CopilotError => "icons/copilot_error.svg", - Icon::CopilotInit => "icons/copilot_init.svg", - Icon::Copy => "icons/copy.svg", - Icon::Dash => "icons/dash.svg", - Icon::Delete => "icons/delete.svg", - Icon::Disconnected => "icons/disconnected.svg", - Icon::Ellipsis => "icons/ellipsis.svg", - Icon::Envelope => "icons/feedback.svg", - Icon::Escape => "icons/escape.svg", - Icon::ExclamationTriangle => "icons/warning.svg", - Icon::Exit => "icons/exit.svg", - Icon::ExternalLink => "icons/external_link.svg", - Icon::File => "icons/file.svg", - Icon::FileDoc => "icons/file_icons/book.svg", - Icon::FileGeneric => "icons/file_icons/file.svg", - Icon::FileGit => "icons/file_icons/git.svg", - Icon::FileLock => "icons/file_icons/lock.svg", - Icon::FileRust => "icons/file_icons/rust.svg", - Icon::FileToml => "icons/file_icons/toml.svg", - Icon::FileTree => "icons/project.svg", - Icon::Filter => "icons/filter.svg", - Icon::Folder => "icons/file_icons/folder.svg", - Icon::FolderOpen => "icons/file_icons/folder_open.svg", - Icon::FolderX => "icons/stop_sharing.svg", - Icon::Github => "icons/github.svg", - Icon::Hash => "icons/hash.svg", - Icon::InlayHint => "icons/inlay_hint.svg", - Icon::Link => "icons/link.svg", - Icon::MagicWand => "icons/magic_wand.svg", - Icon::MagnifyingGlass => "icons/magnifying_glass.svg", - Icon::MailOpen => "icons/mail_open.svg", - Icon::Maximize => "icons/maximize.svg", - Icon::Menu => "icons/menu.svg", - Icon::MessageBubbles => "icons/conversations.svg", - Icon::Mic => "icons/mic.svg", - Icon::MicMute => "icons/mic_mute.svg", - Icon::Minimize => "icons/minimize.svg", - Icon::Option => "icons/option.svg", - Icon::PageDown => "icons/page_down.svg", - Icon::PageUp => "icons/page_up.svg", - Icon::Plus => "icons/plus.svg", - Icon::Public => "icons/public.svg", - Icon::Quote => "icons/quote.svg", - Icon::Replace => "icons/replace.svg", - Icon::ReplaceAll => "icons/replace_all.svg", - Icon::ReplaceNext => "icons/replace_next.svg", - Icon::Return => "icons/return.svg", - Icon::Screen => "icons/desktop.svg", - Icon::SelectAll => "icons/select_all.svg", - Icon::Shift => "icons/shift.svg", - Icon::Snip => "icons/snip.svg", - Icon::Space => "icons/space.svg", - Icon::Split => "icons/split.svg", - Icon::Tab => "icons/tab.svg", - Icon::Terminal => "icons/terminal.svg", - Icon::Update => "icons/update.svg", - Icon::WholeWord => "icons/word_search.svg", - Icon::XCircle => "icons/error.svg", - Icon::ZedXCopilot => "icons/zed_x_copilot.svg", + IconName::Ai => "icons/ai.svg", + IconName::ArrowDown => "icons/arrow_down.svg", + IconName::ArrowLeft => "icons/arrow_left.svg", + IconName::ArrowRight => "icons/arrow_right.svg", + IconName::ArrowUp => "icons/arrow_up.svg", + IconName::ArrowUpRight => "icons/arrow_up_right.svg", + IconName::ArrowCircle => "icons/arrow_circle.svg", + IconName::AtSign => "icons/at_sign.svg", + IconName::AudioOff => "icons/speaker_off.svg", + IconName::AudioOn => "icons/speaker_loud.svg", + IconName::Backspace => "icons/backspace.svg", + IconName::Bell => "icons/bell.svg", + IconName::BellOff => "icons/bell_off.svg", + IconName::BellRing => "icons/bell_ring.svg", + IconName::Bolt => "icons/bolt.svg", + IconName::CaseSensitive => "icons/case_insensitive.svg", + IconName::Check => "icons/check.svg", + IconName::ChevronDown => "icons/chevron_down.svg", + IconName::ChevronLeft => "icons/chevron_left.svg", + IconName::ChevronRight => "icons/chevron_right.svg", + IconName::ChevronUp => "icons/chevron_up.svg", + IconName::Close => "icons/x.svg", + IconName::Collab => "icons/user_group_16.svg", + IconName::Command => "icons/command.svg", + IconName::Control => "icons/control.svg", + IconName::Copilot => "icons/copilot.svg", + IconName::CopilotDisabled => "icons/copilot_disabled.svg", + IconName::CopilotError => "icons/copilot_error.svg", + IconName::CopilotInit => "icons/copilot_init.svg", + IconName::Copy => "icons/copy.svg", + IconName::Dash => "icons/dash.svg", + IconName::Delete => "icons/delete.svg", + IconName::Disconnected => "icons/disconnected.svg", + IconName::Ellipsis => "icons/ellipsis.svg", + IconName::Envelope => "icons/feedback.svg", + IconName::Escape => "icons/escape.svg", + IconName::ExclamationTriangle => "icons/warning.svg", + IconName::Exit => "icons/exit.svg", + IconName::ExternalLink => "icons/external_link.svg", + IconName::File => "icons/file.svg", + IconName::FileDoc => "icons/file_icons/book.svg", + IconName::FileGeneric => "icons/file_icons/file.svg", + IconName::FileGit => "icons/file_icons/git.svg", + IconName::FileLock => "icons/file_icons/lock.svg", + IconName::FileRust => "icons/file_icons/rust.svg", + IconName::FileToml => "icons/file_icons/toml.svg", + IconName::FileTree => "icons/project.svg", + IconName::Filter => "icons/filter.svg", + IconName::Folder => "icons/file_icons/folder.svg", + IconName::FolderOpen => "icons/file_icons/folder_open.svg", + IconName::FolderX => "icons/stop_sharing.svg", + IconName::Github => "icons/github.svg", + IconName::Hash => "icons/hash.svg", + IconName::InlayHint => "icons/inlay_hint.svg", + IconName::Link => "icons/link.svg", + IconName::MagicWand => "icons/magic_wand.svg", + IconName::MagnifyingGlass => "icons/magnifying_glass.svg", + IconName::MailOpen => "icons/mail_open.svg", + IconName::Maximize => "icons/maximize.svg", + IconName::Menu => "icons/menu.svg", + IconName::MessageBubbles => "icons/conversations.svg", + IconName::Mic => "icons/mic.svg", + IconName::MicMute => "icons/mic_mute.svg", + IconName::Minimize => "icons/minimize.svg", + IconName::Option => "icons/option.svg", + IconName::PageDown => "icons/page_down.svg", + IconName::PageUp => "icons/page_up.svg", + IconName::Plus => "icons/plus.svg", + IconName::Public => "icons/public.svg", + IconName::Quote => "icons/quote.svg", + IconName::Replace => "icons/replace.svg", + IconName::ReplaceAll => "icons/replace_all.svg", + IconName::ReplaceNext => "icons/replace_next.svg", + IconName::Return => "icons/return.svg", + IconName::Screen => "icons/desktop.svg", + IconName::SelectAll => "icons/select_all.svg", + IconName::Shift => "icons/shift.svg", + IconName::Snip => "icons/snip.svg", + IconName::Space => "icons/space.svg", + IconName::Split => "icons/split.svg", + IconName::Tab => "icons/tab.svg", + IconName::Terminal => "icons/terminal.svg", + IconName::Update => "icons/update.svg", + IconName::WholeWord => "icons/word_search.svg", + IconName::XCircle => "icons/error.svg", + IconName::ZedXCopilot => "icons/zed_x_copilot.svg", } } } #[derive(IntoElement)] -pub struct IconElement { +pub struct Icon { path: SharedString, color: Color, size: IconSize, } -impl RenderOnce for IconElement { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - svg() - .size(self.size.rems()) - .flex_none() - .path(self.path) - .text_color(self.color.color(cx)) - } -} - -impl IconElement { - pub fn new(icon: Icon) -> Self { +impl Icon { + pub fn new(icon: IconName) -> Self { Self { path: icon.path().into(), color: Color::default(), @@ -248,3 +238,13 @@ impl IconElement { self } } + +impl RenderOnce for Icon { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + svg() + .size(self.size.rems()) + .flex_none() + .path(self.path) + .text_color(self.color.color(cx)) + } +} diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 671f9810831649f8f83dac2e25f88f2223febc40..e0e0583b7cb25e4966c183ae54d9f4742c66935d 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, Icon, IconElement, IconSize}; +use crate::{h_stack, prelude::*, Icon, IconName, IconSize}; use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; #[derive(IntoElement, Clone)] @@ -26,16 +26,16 @@ impl RenderOnce for KeyBinding { .text_color(cx.theme().colors().text_muted) .when(keystroke.modifiers.function, |el| el.child(Key::new("fn"))) .when(keystroke.modifiers.control, |el| { - el.child(KeyIcon::new(Icon::Control)) + el.child(KeyIcon::new(IconName::Control)) }) .when(keystroke.modifiers.alt, |el| { - el.child(KeyIcon::new(Icon::Option)) + el.child(KeyIcon::new(IconName::Option)) }) .when(keystroke.modifiers.command, |el| { - el.child(KeyIcon::new(Icon::Command)) + el.child(KeyIcon::new(IconName::Command)) }) .when(keystroke.modifiers.shift, |el| { - el.child(KeyIcon::new(Icon::Shift)) + el.child(KeyIcon::new(IconName::Shift)) }) .when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon))) .when(key_icon.is_none(), |el| { @@ -62,21 +62,21 @@ impl KeyBinding { Some(Self::new(key_binding)) } - fn icon_for_key(keystroke: &Keystroke) -> Option { + fn icon_for_key(keystroke: &Keystroke) -> Option { match keystroke.key.as_str() { - "left" => Some(Icon::ArrowLeft), - "right" => Some(Icon::ArrowRight), - "up" => Some(Icon::ArrowUp), - "down" => Some(Icon::ArrowDown), - "backspace" => Some(Icon::Backspace), - "delete" => Some(Icon::Delete), - "return" => Some(Icon::Return), - "enter" => Some(Icon::Return), - "tab" => Some(Icon::Tab), - "space" => Some(Icon::Space), - "escape" => Some(Icon::Escape), - "pagedown" => Some(Icon::PageDown), - "pageup" => Some(Icon::PageUp), + "left" => Some(IconName::ArrowLeft), + "right" => Some(IconName::ArrowRight), + "up" => Some(IconName::ArrowUp), + "down" => Some(IconName::ArrowDown), + "backspace" => Some(IconName::Backspace), + "delete" => Some(IconName::Delete), + "return" => Some(IconName::Return), + "enter" => Some(IconName::Return), + "tab" => Some(IconName::Tab), + "space" => Some(IconName::Space), + "escape" => Some(IconName::Escape), + "pagedown" => Some(IconName::PageDown), + "pageup" => Some(IconName::PageUp), _ => None, } } @@ -120,13 +120,13 @@ impl Key { #[derive(IntoElement)] pub struct KeyIcon { - icon: Icon, + icon: IconName, } impl RenderOnce for KeyIcon { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { div().w(rems(14. / 16.)).child( - IconElement::new(self.icon) + Icon::new(self.icon) .size(IconSize::Small) .color(Color::Muted), ) @@ -134,7 +134,7 @@ impl RenderOnce for KeyIcon { } impl KeyIcon { - pub fn new(icon: Icon) -> Self { + pub fn new(icon: IconName) -> Self { Self { icon } } } diff --git a/crates/ui/src/components/list/list_sub_header.rs b/crates/ui/src/components/list/list_sub_header.rs index 2e976b35178a2132cf1f0a6f2c154f03de249389..fc9f35e175c0d42a1517fc1227a4befc7dfdb2da 100644 --- a/crates/ui/src/components/list/list_sub_header.rs +++ b/crates/ui/src/components/list/list_sub_header.rs @@ -1,10 +1,10 @@ use crate::prelude::*; -use crate::{h_stack, Icon, IconElement, IconSize, Label}; +use crate::{h_stack, Icon, IconName, IconSize, Label}; #[derive(IntoElement)] pub struct ListSubHeader { label: SharedString, - start_slot: Option, + start_slot: Option, inset: bool, } @@ -17,7 +17,7 @@ impl ListSubHeader { } } - pub fn left_icon(mut self, left_icon: Option) -> Self { + pub fn left_icon(mut self, left_icon: Option) -> Self { self.start_slot = left_icon; self } @@ -40,11 +40,10 @@ impl RenderOnce for ListSubHeader { .flex() .gap_1() .items_center() - .children(self.start_slot.map(|i| { - IconElement::new(i) - .color(Color::Muted) - .size(IconSize::Small) - })) + .children( + self.start_slot + .map(|i| Icon::new(i).color(Color::Muted).size(IconSize::Small)), + ) .child(Label::new(self.label.clone()).color(Color::Muted)), ), ) diff --git a/crates/ui/src/components/stories/button.rs b/crates/ui/src/components/stories/button.rs index 7240812fa5a3a0d964a811795f925439def97670..c3fcdc5ae913974dcd86519715ae18ba016084fa 100644 --- a/crates/ui/src/components/stories/button.rs +++ b/crates/ui/src/components/stories/button.rs @@ -1,7 +1,7 @@ use gpui::Render; use story::Story; -use crate::{prelude::*, Icon}; +use crate::{prelude::*, IconName}; use crate::{Button, ButtonStyle}; pub struct ButtonStory; @@ -23,12 +23,12 @@ impl Render for ButtonStory { .child(Story::label("With `label_color`")) .child(Button::new("filled_with_label_color", "Click me").color(Color::Created)) .child(Story::label("With `icon`")) - .child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit)) + .child(Button::new("filled_with_icon", "Click me").icon(IconName::FileGit)) .child(Story::label("Selected with `icon`")) .child( Button::new("filled_and_selected_with_icon", "Click me") .selected(true) - .icon(Icon::FileGit), + .icon(IconName::FileGit), ) .child(Story::label("Default (Subtle)")) .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle)) diff --git a/crates/ui/src/components/stories/icon.rs b/crates/ui/src/components/stories/icon.rs index 83fc5980dd731bfbaa61770d58c446c47624f888..f6e750de2add47059ecc630bbc85780cfa273872 100644 --- a/crates/ui/src/components/stories/icon.rs +++ b/crates/ui/src/components/stories/icon.rs @@ -3,17 +3,17 @@ use story::Story; use strum::IntoEnumIterator; use crate::prelude::*; -use crate::{Icon, IconElement}; +use crate::{Icon, IconName}; pub struct IconStory; impl Render for IconStory { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - let icons = Icon::iter(); + let icons = IconName::iter(); Story::container() - .child(Story::title_for::()) + .child(Story::title_for::()) .child(Story::label("All Icons")) - .child(div().flex().gap_3().children(icons.map(IconElement::new))) + .child(div().flex().gap_3().children(icons.map(Icon::new))) } } diff --git a/crates/ui/src/components/stories/icon_button.rs b/crates/ui/src/components/stories/icon_button.rs index 66fc4affb3b0b63daa8dabe4461b9ec806c9416c..6a67183e97c73b3795fc14a71c11a16b6012f549 100644 --- a/crates/ui/src/components/stories/icon_button.rs +++ b/crates/ui/src/components/stories/icon_button.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::{StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, Tooltip}; -use crate::{Icon, IconButton}; +use crate::{IconButton, IconName}; pub struct IconButtonStory; @@ -10,7 +10,7 @@ impl Render for IconButtonStory { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { let default_button = StoryItem::new( "Default", - IconButton::new("default_icon_button", Icon::Hash), + IconButton::new("default_icon_button", IconName::Hash), ) .description("Displays an icon button.") .usage( @@ -21,7 +21,7 @@ impl Render for IconButtonStory { let selected_button = StoryItem::new( "Selected", - IconButton::new("selected_icon_button", Icon::Hash).selected(true), + IconButton::new("selected_icon_button", IconName::Hash).selected(true), ) .description("Displays an icon button that is selected.") .usage( @@ -32,9 +32,9 @@ impl Render for IconButtonStory { let selected_with_selected_icon = StoryItem::new( "Selected with `selected_icon`", - IconButton::new("selected_with_selected_icon_button", Icon::AudioOn) + IconButton::new("selected_with_selected_icon_button", IconName::AudioOn) .selected(true) - .selected_icon(Icon::AudioOff), + .selected_icon(IconName::AudioOff), ) .description( "Displays an icon button that is selected and shows a different icon when selected.", @@ -49,7 +49,7 @@ impl Render for IconButtonStory { let disabled_button = StoryItem::new( "Disabled", - IconButton::new("disabled_icon_button", Icon::Hash).disabled(true), + IconButton::new("disabled_icon_button", IconName::Hash).disabled(true), ) .description("Displays an icon button that is disabled.") .usage( @@ -60,7 +60,7 @@ impl Render for IconButtonStory { let with_on_click_button = StoryItem::new( "With `on_click`", - IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| { + IconButton::new("with_on_click_button", IconName::Ai).on_click(|_event, _cx| { println!("Clicked!"); }), ) @@ -75,7 +75,7 @@ impl Render for IconButtonStory { let with_tooltip_button = StoryItem::new( "With `tooltip`", - IconButton::new("with_tooltip_button", Icon::MessageBubbles) + IconButton::new("with_tooltip_button", IconName::MessageBubbles) .tooltip(|cx| Tooltip::text("Open messages", cx)), ) .description("Displays an icon button that has a tooltip when hovered.") @@ -88,7 +88,7 @@ impl Render for IconButtonStory { let selected_with_tooltip_button = StoryItem::new( "Selected with `tooltip`", - IconButton::new("selected_with_tooltip_button", Icon::InlayHint) + IconButton::new("selected_with_tooltip_button", IconName::InlayHint) .selected(true) .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), ) diff --git a/crates/ui/src/components/stories/list_header.rs b/crates/ui/src/components/stories/list_header.rs index ffbf7157f5b17271633ad72b4ee54023272a831a..358dc26a875c6735373bd22c50edc904ea635597 100644 --- a/crates/ui/src/components/stories/list_header.rs +++ b/crates/ui/src/components/stories/list_header.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::Story; use crate::{prelude::*, IconButton}; -use crate::{Icon, ListHeader}; +use crate::{IconName, ListHeader}; pub struct ListHeaderStory; @@ -13,19 +13,19 @@ impl Render for ListHeaderStory { .child(Story::label("Default")) .child(ListHeader::new("Section 1")) .child(Story::label("With left icon")) - .child(ListHeader::new("Section 2").start_slot(IconElement::new(Icon::Bell))) + .child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell))) .child(Story::label("With left icon and meta")) .child( ListHeader::new("Section 3") - .start_slot(IconElement::new(Icon::BellOff)) - .end_slot(IconButton::new("action_1", Icon::Bolt)), + .start_slot(Icon::new(IconName::BellOff)) + .end_slot(IconButton::new("action_1", IconName::Bolt)), ) .child(Story::label("With multiple meta")) .child( ListHeader::new("Section 4") - .end_slot(IconButton::new("action_1", Icon::Bolt)) - .end_slot(IconButton::new("action_2", Icon::ExclamationTriangle)) - .end_slot(IconButton::new("action_3", Icon::Plus)), + .end_slot(IconButton::new("action_1", IconName::Bolt)) + .end_slot(IconButton::new("action_2", IconName::ExclamationTriangle)) + .end_slot(IconButton::new("action_3", IconName::Plus)), ) } } diff --git a/crates/ui/src/components/stories/list_item.rs b/crates/ui/src/components/stories/list_item.rs index b3ff096d9dfe9d435e68dc7e818b661603267ca2..487f14279178cbaf3d6d4baaab5e5c2f2609eb7a 100644 --- a/crates/ui/src/components/stories/list_item.rs +++ b/crates/ui/src/components/stories/list_item.rs @@ -2,7 +2,7 @@ use gpui::Render; use story::Story; use crate::{prelude::*, Avatar}; -use crate::{Icon, ListItem}; +use crate::{IconName, ListItem}; pub struct ListItemStory; @@ -18,13 +18,13 @@ impl Render for ListItemStory { ListItem::new("inset_list_item") .inset(true) .start_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ) .child("Hello, world!") .end_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ), @@ -34,7 +34,7 @@ impl Render for ListItemStory { ListItem::new("with start slot_icon") .child("Hello, world!") .start_slot( - IconElement::new(Icon::Bell) + Icon::new(IconName::Bell) .size(IconSize::Small) .color(Color::Muted), ), diff --git a/crates/ui/src/components/stories/tab.rs b/crates/ui/src/components/stories/tab.rs index 4c63e593aaa9eab582351b62b618c7e6cae3978c..bd7b602620938775b32be3d46a41b519f2639f63 100644 --- a/crates/ui/src/components/stories/tab.rs +++ b/crates/ui/src/components/stories/tab.rs @@ -27,7 +27,7 @@ impl Render for TabStory { h_stack().child( Tab::new("tab_1") .end_slot( - IconButton::new("close_button", Icon::Close) + IconButton::new("close_button", IconName::Close) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall), diff --git a/crates/ui/src/components/stories/tab_bar.rs b/crates/ui/src/components/stories/tab_bar.rs index 805725315c672a34feac758f52991d0cb3d03f01..289ceff9a6f576739daacd02b57e260b295a7ae8 100644 --- a/crates/ui/src/components/stories/tab_bar.rs +++ b/crates/ui/src/components/stories/tab_bar.rs @@ -38,16 +38,19 @@ impl Render for TabBarStory { h_stack().child( TabBar::new("tab_bar_1") .start_child( - IconButton::new("navigate_backward", Icon::ArrowLeft) + IconButton::new("navigate_backward", IconName::ArrowLeft) .icon_size(IconSize::Small), ) .start_child( - IconButton::new("navigate_forward", Icon::ArrowRight) + IconButton::new("navigate_forward", IconName::ArrowRight) .icon_size(IconSize::Small), ) - .end_child(IconButton::new("new", Icon::Plus).icon_size(IconSize::Small)) .end_child( - IconButton::new("split_pane", Icon::Split).icon_size(IconSize::Small), + IconButton::new("new", IconName::Plus).icon_size(IconSize::Small), + ) + .end_child( + IconButton::new("split_pane", IconName::Split) + .icon_size(IconSize::Small), ) .children(tabs), ), diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 48536f59b312b5389e9892b6593919ed0adc7bd0..0a86b99f9f232927d9e92615adcf987997df7919 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -15,6 +15,6 @@ pub use crate::{h_stack, v_stack}; pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; pub use crate::{ButtonCommon, Color, StyledExt}; pub use crate::{Headline, HeadlineSize}; -pub use crate::{Icon, IconElement, IconPosition, IconSize}; +pub use crate::{Icon, IconName, IconPosition, IconSize}; pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; pub use theme::ActiveTheme; diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index adc4fb5c80b2fd35af7ae32a620a550c977e469a..ed03695c5f2c234f3e7a633cb341da6002d344cd 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -28,7 +28,7 @@ pub trait Panel: FocusableView + EventEmitter { fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn size(&self, cx: &WindowContext) -> Pixels; fn set_size(&mut self, size: Option, cx: &mut ViewContext); - fn icon(&self, cx: &WindowContext) -> Option; + fn icon(&self, cx: &WindowContext) -> Option; fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>; fn toggle_action(&self) -> Box; fn icon_label(&self, _: &WindowContext) -> Option { @@ -52,7 +52,7 @@ pub trait PanelHandle: Send + Sync { fn set_active(&self, active: bool, cx: &mut WindowContext); fn size(&self, cx: &WindowContext) -> Pixels; fn set_size(&self, size: Option, cx: &mut WindowContext); - fn icon(&self, cx: &WindowContext) -> Option; + fn icon(&self, cx: &WindowContext) -> Option; fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>; fn toggle_action(&self, cx: &WindowContext) -> Box; fn icon_label(&self, cx: &WindowContext) -> Option; @@ -104,7 +104,7 @@ where self.update(cx, |this, cx| this.set_size(size, cx)) } - fn icon(&self, cx: &WindowContext) -> Option { + fn icon(&self, cx: &WindowContext) -> Option { self.read(cx).icon(cx) } @@ -774,7 +774,7 @@ pub mod test { self.size = size.unwrap_or(px(300.)); } - fn icon(&self, _: &WindowContext) -> Option { + fn icon(&self, _: &WindowContext) -> Option { None } diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index d70de8c13dc7b45522952474506d609e1a75788b..36628290bb83ed6f640206f20f77933a577f58e3 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -175,7 +175,7 @@ pub mod simple_message_notification { }; use std::sync::Arc; use ui::prelude::*; - use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt}; + use ui::{h_stack, v_stack, Button, Icon, IconName, Label, StyledExt}; pub struct MessageNotification { message: SharedString, @@ -230,7 +230,7 @@ pub mod simple_message_notification { .child( div() .id("cancel") - .child(IconElement::new(Icon::Close)) + .child(Icon::new(IconName::Close)) .cursor_pointer() .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), ), diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 04a51fc655be0d7b5d2b890479bef484b5cbc14a..2a434b32d928bcd7d59de0bca03dad5c8d79d031 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -31,8 +31,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label, - Tab, TabBar, TabPosition, Tooltip, + prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator, + Label, Tab, TabBar, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; @@ -384,7 +384,7 @@ impl Pane { h_stack() .gap_2() .child( - IconButton::new("plus", Icon::Plus) + IconButton::new("plus", IconName::Plus) .icon_size(IconSize::Small) .icon_color(Color::Muted) .on_click(cx.listener(|pane, _, cx| { @@ -406,7 +406,7 @@ impl Pane { el.child(Self::render_menu_overlay(new_item_menu)) }) .child( - IconButton::new("split", Icon::Split) + IconButton::new("split", IconName::Split) .icon_size(IconSize::Small) .icon_color(Color::Muted) .on_click(cx.listener(|pane, _, cx| { @@ -427,11 +427,11 @@ impl Pane { ) .child({ let zoomed = pane.is_zoomed(); - IconButton::new("toggle_zoom", Icon::Maximize) + IconButton::new("toggle_zoom", IconName::Maximize) .icon_size(IconSize::Small) .icon_color(Color::Muted) .selected(zoomed) - .selected_icon(Icon::Minimize) + .selected_icon(IconName::Minimize) .on_click(cx.listener(|pane, _, cx| { pane.toggle_zoom(&crate::ToggleZoom, cx); })) @@ -1570,7 +1570,7 @@ impl Pane { }) .start_slot::(indicator) .end_slot( - IconButton::new("close tab", Icon::Close) + IconButton::new("close tab", IconName::Close) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall) @@ -1676,7 +1676,7 @@ impl Pane { h_stack() .gap_2() .child( - IconButton::new("navigate_backward", Icon::ArrowLeft) + IconButton::new("navigate_backward", IconName::ArrowLeft) .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); @@ -1686,7 +1686,7 @@ impl Pane { .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)), ) .child( - IconButton::new("navigate_forward", Icon::ArrowRight) + IconButton::new("navigate_forward", IconName::ArrowRight) .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index edfabed60d3a03e2290fb94dc9c8482a7e9b4a5e..5b1ca6477ee99b47ad7abc08e64e0421444c5dfd 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -12,7 +12,7 @@ use gpui::{ WindowContext, }; use std::sync::{Arc, Weak}; -use ui::{h_stack, prelude::*, Icon, IconElement, Label}; +use ui::{h_stack, prelude::*, Icon, IconName, Label}; pub enum Event { Close, @@ -100,7 +100,7 @@ impl Item for SharedScreen { ) -> gpui::AnyElement { h_stack() .gap_1() - .child(IconElement::new(Icon::Screen)) + .child(Icon::new(IconName::Screen)) .child( Label::new(format!("{}'s screen", self.user.github_login)).color(if selected { Color::Default From f0ef63bfa0a6cab1676d27ae75e839c2e7666e3c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 10:55:49 -0500 Subject: [PATCH 025/334] gpui: Add `SharedUrl` type (#3975) This PR adds a `SharedUrl` type to GPUI. It's just like a `SharedString`, but for denoting that the contained value is a URL. Mainlined from @nathansobo's GPUI blog post: https://github.com/zed-industries/zed/pull/3968/files#diff-7ee75937e2daf7dd53f71b17698d8bd6d46993d06928d411781b9bd739b5f231R9-R12 Release Notes: - N/A --- crates/client/src/user.rs | 4 +-- .../src/notifications/collab_notification.rs | 6 ++--- crates/gpui/src/elements/img.rs | 8 +++--- crates/gpui/src/gpui.rs | 2 ++ crates/gpui/src/image_cache.rs | 6 ++--- crates/gpui/src/shared_url.rs | 25 +++++++++++++++++++ crates/ui/src/components/stories/list_item.rs | 18 ++++++------- 7 files changed, 48 insertions(+), 21 deletions(-) create mode 100644 crates/gpui/src/shared_url.rs diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 1c288c875db39e3d3d7aed81ed836c1a69b41f5e..4453bb40eaaf34291cfa5b7f9cc917cd8606706f 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result}; use collections::{hash_map::Entry, HashMap, HashSet}; use feature_flags::FeatureFlagAppExt; use futures::{channel::mpsc, Future, StreamExt}; -use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, Task}; +use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task}; use postage::{sink::Sink, watch}; use rpc::proto::{RequestMessage, UsersResponse}; use std::sync::{Arc, Weak}; @@ -19,7 +19,7 @@ pub struct ParticipantIndex(pub u32); pub struct User { pub id: UserId, pub github_login: String, - pub avatar_uri: SharedString, + pub avatar_uri: SharedUrl, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/collab_ui/src/notifications/collab_notification.rs b/crates/collab_ui/src/notifications/collab_notification.rs index e2ef06f9a58f8b131f166ab2a83250839f076ca2..fa0b0a1b14782b8bbe586348487228d75df743f7 100644 --- a/crates/collab_ui/src/notifications/collab_notification.rs +++ b/crates/collab_ui/src/notifications/collab_notification.rs @@ -1,10 +1,10 @@ -use gpui::{img, prelude::*, AnyElement}; +use gpui::{img, prelude::*, AnyElement, SharedUrl}; use smallvec::SmallVec; use ui::prelude::*; #[derive(IntoElement)] pub struct CollabNotification { - avatar_uri: SharedString, + avatar_uri: SharedUrl, accept_button: Button, dismiss_button: Button, children: SmallVec<[AnyElement; 2]>, @@ -12,7 +12,7 @@ pub struct CollabNotification { impl CollabNotification { pub fn new( - avatar_uri: impl Into, + avatar_uri: impl Into, accept_button: Button, dismiss_button: Button, ) -> Self { diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 650b5b666bc821e15873c53f2cdc56c0546b6a20..71a51351fdb24ccf4b275b42e2541a87569014dd 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::{ point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement, - InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedString, Size, + InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; @@ -12,13 +12,13 @@ use util::ResultExt; #[derive(Clone, Debug)] pub enum ImageSource { /// Image content will be loaded from provided URI at render time. - Uri(SharedString), + Uri(SharedUrl), Data(Arc), Surface(CVImageBuffer), } -impl From for ImageSource { - fn from(value: SharedString) -> Self { +impl From for ImageSource { + fn from(value: SharedUrl) -> Self { Self::Uri(value) } } diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index d5236d8f08c7f2fa4cb3551ee5a58695634f7e8a..6f5e30149d9691b3c364d62ab2e3ce6ec7da1b4c 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -18,6 +18,7 @@ mod platform; pub mod prelude; mod scene; mod shared_string; +mod shared_url; mod style; mod styled; mod subscription; @@ -67,6 +68,7 @@ pub use refineable::*; pub use scene::*; use seal::Sealed; pub use shared_string::*; +pub use shared_url::*; pub use smol::Timer; pub use style::*; pub use styled::*; diff --git a/crates/gpui/src/image_cache.rs b/crates/gpui/src/image_cache.rs index f80b0f0c2f71a60fa91dbf87a13ffa3b86f43abf..0d6ec81557aa7b21165a628cf707f0d1caded9d0 100644 --- a/crates/gpui/src/image_cache.rs +++ b/crates/gpui/src/image_cache.rs @@ -1,4 +1,4 @@ -use crate::{ImageData, ImageId, SharedString}; +use crate::{ImageData, ImageId, SharedUrl}; use collections::HashMap; use futures::{ future::{BoxFuture, Shared}, @@ -44,7 +44,7 @@ impl From for Error { pub struct ImageCache { client: Arc, - images: Arc>>, + images: Arc>>, } type FetchImageFuture = Shared, Error>>>; @@ -59,7 +59,7 @@ impl ImageCache { pub fn get( &self, - uri: impl Into, + uri: impl Into, ) -> Shared, Error>>> { let uri = uri.into(); let mut images = self.images.lock(); diff --git a/crates/gpui/src/shared_url.rs b/crates/gpui/src/shared_url.rs new file mode 100644 index 0000000000000000000000000000000000000000..8fb901894367d3fa4d87e78d8b95e1a47df0ce10 --- /dev/null +++ b/crates/gpui/src/shared_url.rs @@ -0,0 +1,25 @@ +use derive_more::{Deref, DerefMut}; + +use crate::SharedString; + +/// A [`SharedString`] containing a URL. +#[derive(Deref, DerefMut, Default, PartialEq, Eq, Hash, Clone)] +pub struct SharedUrl(SharedString); + +impl std::fmt::Debug for SharedUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::fmt::Display for SharedUrl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.as_ref()) + } +} + +impl> From for SharedUrl { + fn from(value: T) -> Self { + Self(value.into()) + } +} diff --git a/crates/ui/src/components/stories/list_item.rs b/crates/ui/src/components/stories/list_item.rs index 487f14279178cbaf3d6d4baaab5e5c2f2609eb7a..a25b07df849aeb67a185e3984ab11ec341b07045 100644 --- a/crates/ui/src/components/stories/list_item.rs +++ b/crates/ui/src/components/stories/list_item.rs @@ -1,4 +1,4 @@ -use gpui::Render; +use gpui::{Render, SharedUrl}; use story::Story; use crate::{prelude::*, Avatar}; @@ -43,7 +43,7 @@ impl Render for ListItemStory { .child( ListItem::new("with_start slot avatar") .child("Hello, world!") - .start_slot(Avatar::new(SharedString::from( + .start_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) @@ -51,7 +51,7 @@ impl Render for ListItemStory { .child( ListItem::new("with_left_avatar") .child("Hello, world!") - .end_slot(Avatar::new(SharedString::from( + .end_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) @@ -62,23 +62,23 @@ impl Render for ListItemStory { .end_slot( h_stack() .gap_2() - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))) - .child(Avatar::new(SharedString::from( + .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", ))), ) - .end_hover_slot(Avatar::new(SharedString::from( + .end_hover_slot(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1714999?v=4", ))), ) From d3749531808f4626b4e790a1eec47d3b4860400b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:16:25 +0100 Subject: [PATCH 026/334] search: Remove newlines from query used for tab_content. (#3976) Fixes https://github.com/zed-industries/community/issues/2388 Release Notes: - Fixed tab content of project search overflowing the tab for queries with newlines. --- crates/search/src/project_search.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 5cdf614c1b9f8e22c416866db3a863e2458251c2..6fd66b5bad2c1c9ab1036a445f1e2061b791f206 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -424,7 +424,8 @@ impl Item for ProjectSearchView { .current() .as_ref() .map(|query| { - let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN); + let query = query.replace('\n', ""); + let query_text = util::truncate_and_trailoff(&query, MAX_TAB_TITLE_LEN); query_text.into() }); let tab_name = last_query From 824d06e2b2d7f06b70c18e56206579263adbbc04 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 11:31:19 -0500 Subject: [PATCH 027/334] Remove `Default` impl for `StatusColors` (#3977) This PR removes the `Default` impl for `StatusColors`. Since we need default light and dark variants for `StatusColors`, we can't use a single `Default` impl. Release Notes: - N/A --- crates/editor/src/editor.rs | 20 +++++++++++++++++++- crates/editor/src/hover_popover.rs | 24 +++++++++++++----------- crates/theme/src/styles/colors.rs | 4 +++- crates/theme/src/styles/status.rs | 9 --------- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9858cf8372eec5a57e4f1eeb419d06147d7ce51c..71fe6ccfcbc743dc8121362e11f0f9431e5ce05d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -507,7 +507,7 @@ pub enum SoftWrap { Column(u32), } -#[derive(Clone, Default)] +#[derive(Clone)] pub struct EditorStyle { pub background: Hsla, pub local_player: PlayerColor, @@ -519,6 +519,24 @@ pub struct EditorStyle { pub suggestions_style: HighlightStyle, } +impl Default for EditorStyle { + fn default() -> Self { + Self { + background: Hsla::default(), + local_player: PlayerColor::default(), + text: TextStyle::default(), + scrollbar_width: Pixels::default(), + syntax: Default::default(), + // HACK: Status colors don't have a real default. + // We should look into removing the status colors from the editor + // style and retrieve them directly from the theme. + status: StatusColors::dark(), + inlays_style: HighlightStyle::default(), + suggestions_style: HighlightStyle::default(), + } + } +} + type CompletionId = usize; // type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor; diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 22c58056f01d660bce7a031782577904881214c4..26ce3e5cf708e0193eef2c87fbf72c27f4da806a 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -16,7 +16,7 @@ use lsp::DiagnosticSeverity; use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart, Project}; use settings::Settings; use std::{ops::Range, sync::Arc, time::Duration}; -use ui::{StyledExt, Tooltip}; +use ui::{prelude::*, Tooltip}; use util::TryFutureExt; use workspace::Workspace; @@ -514,6 +514,8 @@ impl DiagnosticPopover { None => self.local_diagnostic.diagnostic.message.clone(), }; + let status_colors = cx.theme().status(); + struct DiagnosticColors { pub background: Hsla, pub border: Hsla, @@ -521,24 +523,24 @@ impl DiagnosticPopover { let diagnostic_colors = match self.local_diagnostic.diagnostic.severity { DiagnosticSeverity::ERROR => DiagnosticColors { - background: style.status.error_background, - border: style.status.error_border, + background: status_colors.error_background, + border: status_colors.error_border, }, DiagnosticSeverity::WARNING => DiagnosticColors { - background: style.status.warning_background, - border: style.status.warning_border, + background: status_colors.warning_background, + border: status_colors.warning_border, }, DiagnosticSeverity::INFORMATION => DiagnosticColors { - background: style.status.info_background, - border: style.status.info_border, + background: status_colors.info_background, + border: status_colors.info_border, }, DiagnosticSeverity::HINT => DiagnosticColors { - background: style.status.hint_background, - border: style.status.hint_border, + background: status_colors.hint_background, + border: status_colors.hint_border, }, _ => DiagnosticColors { - background: style.status.ignored_background, - border: style.status.ignored_border, + background: status_colors.ignored_background, + border: status_colors.ignored_border, }, }; diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index 5f2295866535deefbe5ff29f51662847b241692e..eb68b6c219f2090a57af9db2c9d1f67ff6dfacb9 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -2,7 +2,7 @@ use gpui::Hsla; use refineable::Refineable; use std::sync::Arc; -use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors}; +use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, SystemColors}; #[derive(Refineable, Clone, Debug)] #[refineable(Debug, serde::Deserialize)] @@ -219,6 +219,8 @@ pub struct ThemeStyles { #[refineable] pub colors: ThemeColors, + + #[refineable] pub status: StatusColors, pub player: PlayerColors, pub syntax: Arc, diff --git a/crates/theme/src/styles/status.rs b/crates/theme/src/styles/status.rs index 0ce166deb59b75a8abd10ea105b5efddd66fa33e..854b876ac20b33702f9f4cce467c162e610ad125 100644 --- a/crates/theme/src/styles/status.rs +++ b/crates/theme/src/styles/status.rs @@ -78,15 +78,6 @@ pub struct StatusColors { pub warning_border: Hsla, } -impl Default for StatusColors { - /// Don't use this! - /// We have to have a default to be `[refineable::Refinable]`. - /// todo!("Find a way to not need this for Refinable") - fn default() -> Self { - Self::dark() - } -} - pub struct DiagnosticColors { pub error: Hsla, pub warning: Hsla, From 3bb29acd26f9d406cbbe6660fb361485792909ec Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 Jan 2024 17:34:57 +0100 Subject: [PATCH 028/334] :lipstick: --- crates/gpui/src/key_dispatch.rs | 45 +++++++++++++++++---------------- crates/gpui/src/window.rs | 16 +++++------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index af836b7166635f85002a00b11b61a8643ab6b31c..c6abc101eabd398ce3beafe7f7c0da85702174e2 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -70,18 +70,35 @@ impl DispatchTree { self.keystroke_matchers.clear(); } - pub fn push_node(&mut self, context: Option) { + pub fn push_node( + &mut self, + context: Option, + focus_id: Option, + view_id: Option, + ) { let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); self.nodes.push(DispatchNode { parent, + focus_id, + view_id, ..Default::default() }); self.node_stack.push(node_id); + if let Some(context) = context { self.active_node().context = Some(context.clone()); self.context_stack.push(context); } + + if let Some(focus_id) = focus_id { + self.focusable_node_ids.insert(focus_id, node_id); + } + + if let Some(view_id) = view_id { + self.view_stack.push(view_id); + self.view_node_ids.insert(view_id, node_id); + } } pub fn pop_node(&mut self) { @@ -96,14 +113,11 @@ impl DispatchTree { } fn move_node(&mut self, source_node: &mut DispatchNode) { - self.push_node(source_node.context.take()); - if let Some(focus_id) = source_node.focus_id { - self.make_focusable(focus_id); - } - if let Some(view_id) = source_node.view_id { - self.associate_view(view_id); - } - + self.push_node( + source_node.context.take(), + source_node.focus_id, + source_node.view_id, + ); let target_node = self.active_node(); target_node.key_listeners = mem::take(&mut source_node.key_listeners); target_node.action_listeners = mem::take(&mut source_node.action_listeners); @@ -195,19 +209,6 @@ impl DispatchTree { }); } - pub fn make_focusable(&mut self, focus_id: FocusId) { - let node_id = self.active_node_id(); - self.active_node().focus_id = Some(focus_id); - self.focusable_node_ids.insert(focus_id, node_id); - } - - pub fn associate_view(&mut self, view_id: EntityId) { - let node_id = self.active_node_id(); - self.active_node().view_id = Some(view_id); - self.view_node_ids.insert(view_id, node_id); - self.view_stack.push(view_id); - } - pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { if parent == child { return true; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index f21b8b3b80d691f2707f4e2c18f2213809c40d36..2748ebed8489a35025a89ac35ab22311522e8c28 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1866,13 +1866,12 @@ impl<'a> WindowContext<'a> { f: impl FnOnce(Option, &mut Self) -> R, ) -> R { let window = &mut self.window; - window.next_frame.dispatch_tree.push_node(context.clone()); - if let Some(focus_handle) = focus_handle.as_ref() { - window - .next_frame - .dispatch_tree - .make_focusable(focus_handle.id); - } + let focus_id = focus_handle.as_ref().map(|handle| handle.id); + window + .next_frame + .dispatch_tree + .push_node(context.clone(), focus_id, None); + let result = f(focus_handle, self); self.window.next_frame.dispatch_tree.pop_node(); @@ -2165,11 +2164,10 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { } fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - self.window_mut().next_frame.dispatch_tree.push_node(None); self.window_mut() .next_frame .dispatch_tree - .associate_view(view_id); + .push_node(None, None, Some(view_id)); let result = f(self); self.window_mut().next_frame.dispatch_tree.pop_node(); result From b801e0a46eb58889f652ad13ab614be80f1ef39c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 09:44:22 -0700 Subject: [PATCH 029/334] Fix merge conflict --- crates/collab/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 87a423bea90df1d2b21a6d85c39f43fbe08a9142..aba9bd75d1f0aa9cc1849309dcb8f8db5b2ed9e3 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -105,7 +105,7 @@ pub struct Config { impl Config { pub fn is_development(&self) -> bool { - self.zed_environment == "development" + self.zed_environment == "development".into() } } From ab6cd1d93aa7c454f97a3c4ea0275468caa47212 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 9 Jan 2024 18:00:54 +0100 Subject: [PATCH 030/334] chore: cfg(test) a test in rpc. (#3978) The test in particular should be included in our release binary at present. :/ Release Notes: - N/A --- Cargo.lock | 23 ++++++++++---- crates/rpc/src/notification.rs | 55 ++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6e7ecebc1300628462750ed714b9103e6a001c6..49af5ec2d47a9b795bb5ae0a4cf2b95c8b78a298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,7 @@ dependencies = [ "tonic", "tower", "tracing", - "tracing-log", + "tracing-log 0.1.3", "tracing-subscriber", "unindent", "util", @@ -6930,9 +6930,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -8316,6 +8316,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + [[package]] name = "tracing-serde" version = "0.1.3" @@ -8328,9 +8339,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -8343,7 +8354,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log", + "tracing-log 0.2.0", "tracing-serde", ] diff --git a/crates/rpc/src/notification.rs b/crates/rpc/src/notification.rs index c5476469be90112e6ae294665c2bc53319cf38be..57131d3421787a71b4df0edefd59a3d7cfef865e 100644 --- a/crates/rpc/src/notification.rs +++ b/crates/rpc/src/notification.rs @@ -76,30 +76,35 @@ impl Notification { } } -#[test] -fn test_notification() { - // Notifications can be serialized and deserialized. - for notification in [ - Notification::ContactRequest { sender_id: 1 }, - Notification::ContactRequestAccepted { responder_id: 2 }, - Notification::ChannelInvitation { - channel_id: 100, - channel_name: "the-channel".into(), - inviter_id: 50, - }, - Notification::ChannelMessageMention { - sender_id: 200, - channel_id: 30, - message_id: 1, - }, - ] { - let message = notification.to_proto(); - let deserialized = Notification::from_proto(&message).unwrap(); - assert_eq!(deserialized, notification); - } +#[cfg(test)] +mod tests { + use crate::Notification; + + #[test] + fn test_notification() { + // Notifications can be serialized and deserialized. + for notification in [ + Notification::ContactRequest { sender_id: 1 }, + Notification::ContactRequestAccepted { responder_id: 2 }, + Notification::ChannelInvitation { + channel_id: 100, + channel_name: "the-channel".into(), + inviter_id: 50, + }, + Notification::ChannelMessageMention { + sender_id: 200, + channel_id: 30, + message_id: 1, + }, + ] { + let message = notification.to_proto(); + let deserialized = Notification::from_proto(&message).unwrap(); + assert_eq!(deserialized, notification); + } - // When notifications are serialized, the `kind` and `actor_id` fields are - // stored separately, and do not appear redundantly in the JSON. - let notification = Notification::ContactRequest { sender_id: 1 }; - assert_eq!(notification.to_proto().content, "{}"); + // When notifications are serialized, the `kind` and `actor_id` fields are + // stored separately, and do not appear redundantly in the JSON. + let notification = Notification::ContactRequest { sender_id: 1 }; + assert_eq!(notification.to_proto().content, "{}"); + } } From 0c6d107740cded0b78b291db25346867c8bf27af Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 9 Jan 2024 18:00:48 +0100 Subject: [PATCH 031/334] Introduce `on_request_frame` --- crates/gpui/src/platform.rs | 5 ++-- crates/gpui/src/platform/mac/platform.rs | 11 ++------ crates/gpui/src/platform/mac/window.rs | 32 +++++++++++++---------- crates/gpui/src/platform/test/platform.rs | 4 +-- crates/gpui/src/platform/test/window.rs | 8 +++--- crates/gpui/src/window.rs | 19 +++++++------- 6 files changed, 37 insertions(+), 42 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 0ef345d98d1de34bbb7c6b567b4716608e2e6dbf..c41bf998f698443d5b6ca93dee2966206596d533 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -44,8 +44,6 @@ pub(crate) fn current_platform() -> Rc { Rc::new(MacPlatform::new()) } -pub type DrawWindow = Box Result>; - pub(crate) trait Platform: 'static { fn background_executor(&self) -> BackgroundExecutor; fn foreground_executor(&self) -> ForegroundExecutor; @@ -66,7 +64,6 @@ pub(crate) trait Platform: 'static { &self, handle: AnyWindowHandle, options: WindowOptions, - draw: DrawWindow, ) -> Box; fn set_display_link_output_callback( @@ -157,6 +154,7 @@ pub trait PlatformWindow { fn minimize(&self); fn zoom(&self); fn toggle_full_screen(&self); + fn on_request_frame(&self, callback: Box); fn on_input(&self, callback: Box bool>); fn on_active_status_change(&self, callback: Box); fn on_resize(&self, callback: Box, f32)>); @@ -167,6 +165,7 @@ pub trait PlatformWindow { fn on_appearance_changed(&self, callback: Box); fn is_topmost_for_position(&self, position: Point) -> bool; fn invalidate(&self); + fn draw(&self, scene: &Scene); fn sprite_atlas(&self) -> Arc; diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 8370e2a4953c1280a59d4a9cb74a93ae97214db2..78c2a0738127e43f36d48f60553a1f854b8ec761 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -3,8 +3,7 @@ use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, - PlatformTextSystem, PlatformWindow, Result, Scene, SemanticVersion, VideoTimestamp, - WindowOptions, + PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -498,14 +497,8 @@ impl Platform for MacPlatform { &self, handle: AnyWindowHandle, options: WindowOptions, - draw: Box Result>, ) -> Box { - Box::new(MacWindow::open( - handle, - options, - draw, - self.foreground_executor(), - )) + Box::new(MacWindow::open(handle, options, self.foreground_executor())) } fn set_display_link_output_callback( diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 2beac528c18f53cfa9a39b008dbebf3825502b30..6f15a7d1acddad42b3427ddf2129d26b4519dd92 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1,6 +1,6 @@ use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange}; use crate::{ - display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, DrawWindow, ExternalPaths, + display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, @@ -46,7 +46,6 @@ use std::{ sync::{Arc, Weak}, time::Duration, }; -use util::ResultExt; const WINDOW_STATE_IVAR: &str = "windowState"; @@ -317,8 +316,8 @@ struct MacWindowState { executor: ForegroundExecutor, native_window: id, renderer: MetalRenderer, - draw: Option, kind: WindowKind, + request_frame_callback: Option>, event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option, f32)>>, @@ -453,7 +452,6 @@ impl MacWindow { pub fn open( handle: AnyWindowHandle, options: WindowOptions, - draw: DrawWindow, executor: ForegroundExecutor, ) -> Self { unsafe { @@ -545,8 +543,8 @@ impl MacWindow { executor, native_window, renderer: MetalRenderer::new(true), - draw: Some(draw), kind: options.kind, + request_frame_callback: None, event_callback: None, activate_callback: None, resize_callback: None, @@ -926,6 +924,10 @@ impl PlatformWindow for MacWindow { .detach(); } + fn on_request_frame(&self, callback: Box) { + self.0.as_ref().lock().request_frame_callback = Some(callback); + } + fn on_input(&self, callback: Box bool>) { self.0.as_ref().lock().event_callback = Some(callback); } @@ -990,6 +992,11 @@ impl PlatformWindow for MacWindow { } } + fn draw(&self, scene: &crate::Scene) { + let mut this = self.0.lock(); + this.renderer.draw(scene); + } + fn sprite_atlas(&self) -> Arc { self.0.lock().renderer.sprite_atlas().clone() } @@ -1462,15 +1469,12 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) { } extern "C" fn display_layer(this: &Object, _: Sel, _: id) { - unsafe { - let window_state = get_window_state(this); - let mut draw = window_state.lock().draw.take().unwrap(); - let scene = draw().log_err(); - let mut window_state = window_state.lock(); - window_state.draw = Some(draw); - if let Some(scene) = scene { - window_state.renderer.draw(&scene); - } + let window_state = unsafe { get_window_state(this) }; + let mut lock = window_state.lock(); + if let Some(mut callback) = lock.request_frame_callback.take() { + drop(lock); + callback(); + window_state.lock().request_frame_callback = Some(callback); } } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 695323e9c46b8e2a8f4260a682d8e214f58c43f4..5067f6a4b3417853a9fe36b59681ddfc5984437f 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,7 +1,6 @@ use crate::{ AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, - Keymap, Platform, PlatformDisplay, PlatformTextSystem, Scene, TestDisplay, TestWindow, - WindowOptions, + Keymap, Platform, PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions, }; use anyhow::{anyhow, Result}; use collections::VecDeque; @@ -162,7 +161,6 @@ impl Platform for TestPlatform { &self, handle: AnyWindowHandle, options: WindowOptions, - _draw: Box Result>, ) -> Box { let window = TestWindow::new( options, diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 91f965c10ac2987f4f6c8c15cb40a7cd94171370..029fd7300998f55f089696d6e01199b9c687ddfd 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -218,6 +218,8 @@ impl PlatformWindow for TestWindow { unimplemented!() } + fn on_request_frame(&self, _callback: Box) {} + fn on_input(&self, callback: Box bool>) { self.0.lock().input_callback = Some(callback) } @@ -254,9 +256,9 @@ impl PlatformWindow for TestWindow { unimplemented!() } - fn invalidate(&self) { - // (self.draw.lock())().unwrap(); - } + fn invalidate(&self) {} + + fn draw(&self, _scene: &crate::Scene) {} fn sprite_atlas(&self) -> sync::Arc { self.0.lock().sprite_atlas.clone() diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 2748ebed8489a35025a89ac35ab22311522e8c28..8f9a82672da6a3c006d788a3e10782403f7c0a17 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -337,14 +337,7 @@ impl Window { options: WindowOptions, cx: &mut AppContext, ) -> Self { - let platform_window = cx.platform.open_window( - handle, - options, - Box::new({ - let mut cx = cx.to_async(); - move || handle.update(&mut cx, |_, cx| cx.draw()) - }), - ); + let platform_window = cx.platform.open_window(handle, options); let display_id = platform_window.display().id(); let sprite_atlas = platform_window.sprite_atlas(); let mouse_position = platform_window.mouse_position(); @@ -353,6 +346,12 @@ impl Window { let scale_factor = platform_window.scale_factor(); let bounds = platform_window.bounds(); + platform_window.on_request_frame(Box::new({ + let mut cx = cx.to_async(); + move || { + handle.update(&mut cx, |_, cx| cx.draw()).log_err(); + } + })); platform_window.on_resize(Box::new({ let mut cx = cx.to_async(); move |_, _| { @@ -1358,7 +1357,7 @@ impl<'a> WindowContext<'a> { } /// Draw pixels to the display for this window based on the contents of its scene. - pub(crate) fn draw(&mut self) -> Scene { + pub(crate) fn draw(&mut self) { println!("====================="); self.window.dirty = false; self.window.drawing = true; @@ -1470,7 +1469,7 @@ impl<'a> WindowContext<'a> { self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); - scene + self.window.platform_window.draw(&scene); } /// Dispatch a mouse or keyboard event on the window. From bcbfa7d0361b18218e3aefce25d072dbc75fbbf9 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 12:49:56 -0500 Subject: [PATCH 032/334] Start documenting Theme crate Co-Authored-By: Antonio Scandurra --- Cargo.lock | 23 ++++----------- crates/theme/src/scale.rs | 49 +++++++++++++------------------ crates/theme/src/settings.rs | 2 +- crates/theme/src/styles/colors.rs | 1 + crates/theme/src/theme.rs | 8 +++++ crates/theme/theme.md | 15 ++++++++++ 6 files changed, 51 insertions(+), 47 deletions(-) create mode 100644 crates/theme/theme.md diff --git a/Cargo.lock b/Cargo.lock index 49af5ec2d47a9b795bb5ae0a4cf2b95c8b78a298..c6e7ecebc1300628462750ed714b9103e6a001c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1515,7 +1515,7 @@ dependencies = [ "tonic", "tower", "tracing", - "tracing-log 0.1.3", + "tracing-log", "tracing-subscriber", "unindent", "util", @@ -6930,9 +6930,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.7" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] @@ -8316,17 +8316,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-serde" version = "0.1.3" @@ -8339,9 +8328,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", "nu-ansi-term", @@ -8354,7 +8343,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", "tracing-serde", ] diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index c1c2ff924c866473fb90f89842f0d32432b9da27..8bfb2999cda97f2da6d4dac816a85f51f3b564e6 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -2,41 +2,31 @@ use gpui::{AppContext, Hsla, SharedString}; use crate::{ActiveTheme, Appearance}; -/// A one-based step in a [`ColorScale`]. +/// A collection of colors that are used to style the UI. +/// +/// Each step has a semantic meaning, and is used to style different parts of the UI. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub struct ColorScaleStep(usize); impl ColorScaleStep { - pub const ONE: Self = Self(1); - pub const TWO: Self = Self(2); - pub const THREE: Self = Self(3); - pub const FOUR: Self = Self(4); - pub const FIVE: Self = Self(5); - pub const SIX: Self = Self(6); - pub const SEVEN: Self = Self(7); - pub const EIGHT: Self = Self(8); - pub const NINE: Self = Self(9); - pub const TEN: Self = Self(10); - pub const ELEVEN: Self = Self(11); - pub const TWELVE: Self = Self(12); - - /// All of the steps in a [`ColorScale`]. - pub const ALL: [ColorScaleStep; 12] = [ - Self::ONE, - Self::TWO, - Self::THREE, - Self::FOUR, - Self::FIVE, - Self::SIX, - Self::SEVEN, - Self::EIGHT, - Self::NINE, - Self::TEN, - Self::ELEVEN, - Self::TWELVE, - ]; + const ONE: Self = Self(1); + const TWO: Self = Self(2); + const THREE: Self = Self(3); + const FOUR: Self = Self(4); + const FIVE: Self = Self(5); + const SIX: Self = Self(6); + const SEVEN: Self = Self(7); + const EIGHT: Self = Self(8); + const NINE: Self = Self(9); + const TEN: Self = Self(10); + const ELEVEN: Self = Self(11); + const TWELVE: Self = Self(12); } +/// A scale of colors for a given [ColorScaleSet]. +/// +/// Each [ColorScale] contains exactly 12 colors. Refer to +/// [ColorScaleStep] for a reference of what each step is used for. pub struct ColorScale(Vec); impl FromIterator for ColorScale { @@ -229,6 +219,7 @@ impl IntoIterator for ColorScales { } } +/// Provides groups of [ColorScale]s for light and dark themes, as well as transparent versions of each scale. pub struct ColorScaleSet { name: SharedString, light: ColorScale, diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 624b14fe33fe194e2f8b7af3211ff4c9a144c100..3ecf1935a47eeb7fb4c8e4edbce9d99f33a2b3f3 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -27,7 +27,7 @@ pub struct ThemeSettings { } #[derive(Default)] -pub struct AdjustedBufferFontSize(Pixels); +pub(crate) struct AdjustedBufferFontSize(Pixels); #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ThemeSettingsContent { diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index eb68b6c219f2090a57af9db2c9d1f67ff6dfacb9..c207aa6bb413321d38d8f6b2787296afee5b7e59 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -7,6 +7,7 @@ use crate::{PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, Sys #[derive(Refineable, Clone, Debug)] #[refineable(Debug, serde::Deserialize)] pub struct ThemeColors { + /// Border color. Used for most borders, is usually a high contrast color. pub border: Hsla, /// Border color. Used for deemphasized borders, like a visual divider between two sections pub border_variant: Hsla, diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 93253b95e7358962707ef0fc22fbe01a01f7cd56..f8d90b7bdc823b0b52348fe94908454002616347 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1,3 +1,11 @@ +//! # Theme +//! +//! This crate provides the theme system for Zed. +//! +//! ## Overview +//! +//! A theme is a collection of colors used to build a consistent appearance for UI components across the application. + mod default_colors; mod default_theme; mod one_themes; diff --git a/crates/theme/theme.md b/crates/theme/theme.md new file mode 100644 index 0000000000000000000000000000000000000000..f9a7a581786eff51b53d1209e6a8518ae60403a5 --- /dev/null +++ b/crates/theme/theme.md @@ -0,0 +1,15 @@ + # Theme + + This crate provides the theme system for Zed. + + ## Overview + + A theme is a collection of colors used to build a consistent appearance for UI components across the application. + To produce a theme in Zed, + + A theme is made of of two parts: A [ThemeFamily] and one or more [Theme]s. + +// + A [ThemeFamily] contains metadata like theme name, author, and theme-specific [ColorScales] as well as a series of themes. + + - [ThemeColors] - A set of colors that are used to style the UI. Refer to the [ThemeColors] documentation for more information. From 9747c10ce8bc0be962c87e10777ce38414068571 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 12:57:12 -0500 Subject: [PATCH 033/334] Document UI modules Co-Authored-By: Antonio Scandurra --- crates/ui/src/prelude.rs | 2 ++ crates/ui/src/ui.rs | 2 -- crates/ui/src/utils.rs | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 0a86b99f9f232927d9e92615adcf987997df7919..ee635e4d6b944d807034087ff1e4de2788505847 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -1,3 +1,5 @@ +//! The prelude of this crate. When building UI in zed you almost always want to import this. + pub use gpui::prelude::*; pub use gpui::{ div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId, diff --git a/crates/ui/src/ui.rs b/crates/ui/src/ui.rs index b074f10dad50b9e61d0ed69ca29f39687e54973e..e8ee51818ea963657aafc2cf1dfdd5f8dbd4668f 100644 --- a/crates/ui/src/ui.rs +++ b/crates/ui/src/ui.rs @@ -3,8 +3,6 @@ //! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI. //! -#![doc = include_str!("../docs/building-ui.md")] - mod clickable; mod components; mod disableable; diff --git a/crates/ui/src/utils.rs b/crates/ui/src/utils.rs index 573a1333efcfa0169a84f52ce27e72838492c696..ed1fec690fc738be2bee5d621c7fc53f0c0f6b9e 100644 --- a/crates/ui/src/utils.rs +++ b/crates/ui/src/utils.rs @@ -1,3 +1,5 @@ +//! UI-related utilities (e.g. converting dates to a human-readable form). + mod format_distance; pub use format_distance::*; From 59f41acb8236aa1f9956404d699ac79036d1210a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:02:57 -0700 Subject: [PATCH 034/334] Add a bunch of docs Co-Authored-By: Conrad Co-Authored-By: Mikayla --- crates/gpui/src/app/entity_map.rs | 5 +- crates/gpui/src/app/test_context.rs | 25 ++++++ crates/gpui/src/executor.rs | 42 +++++++++- crates/gpui/src/test.rs | 28 +++++++ crates/gpui/src/window.rs | 123 ++++++++++++++++++++++++++-- 5 files changed, 214 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 17f6e47ddfec85de1c82b1fc0b8cd6ffc285aa32..0b213b20f769975e22761f8294251051e39e2753 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -19,7 +19,10 @@ use std::{ #[cfg(any(test, feature = "test-support"))] use collections::HashMap; -slotmap::new_key_type! { pub struct EntityId; } +slotmap::new_key_type! { + /// A unique identifier for a model or view across the application. + pub struct EntityId; +} impl EntityId { pub fn as_u64(self) -> u64 { diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index a530aaf69b94ce65d35feee015738f306b3a79c3..587d3f3fec4b5f6d278b4bd1e7272e5b6bf3d356 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -9,6 +9,8 @@ use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; +/// A TestAppContext is provided to tests created with `#[gpui::test]`, it provides +/// an implementation of `Context` with additional methods that are useful in tests. #[derive(Clone)] pub struct TestAppContext { pub app: Rc, @@ -95,38 +97,47 @@ impl TestAppContext { } } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { Self::new(self.dispatcher.clone()) } + /// Simulates quitting the app. pub fn quit(&self) { self.app.borrow_mut().shutdown(); } + /// Schedules all windows to be redrawn on the next effect cycle. pub fn refresh(&mut self) -> Result<()> { let mut app = self.app.borrow_mut(); app.refresh(); Ok(()) } + /// Returns an executor (for running tasks in the background) pub fn executor(&self) -> BackgroundExecutor { self.background_executor.clone() } + /// Returns an executor (for running tasks on the main thread) pub fn foreground_executor(&self) -> &ForegroundExecutor { &self.foreground_executor } + /// Gives you an `&mut AppContext` for the duration of the closure pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { let mut cx = self.app.borrow_mut(); cx.update(f) } + /// Gives you an `&AppContext` for the duration of the closure pub fn read(&self, f: impl FnOnce(&AppContext) -> R) -> R { let cx = self.app.borrow(); f(&*cx) } + // Adds a new window. The Window will always be backed by a `TestWindow` which + // can be retrieved with `self.test_window(handle)` pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, @@ -136,12 +147,16 @@ impl TestAppContext { cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window)) } + // Adds a new window with no content. pub fn add_empty_window(&mut self) -> AnyWindowHandle { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) .any_handle } + /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used + /// as a `WindowContext` for the rest of the test. Typically you would shadow this context with + /// the returned one. `let (view, cx) = cx.add_window_view(...);` pub fn add_window_view(&mut self, build_window: F) -> (View, &mut VisualTestContext) where F: FnOnce(&mut ViewContext) -> V, @@ -156,18 +171,23 @@ impl TestAppContext { (view, Box::leak(cx)) } + /// returns the TextSystem pub fn text_system(&self) -> &Arc { &self.text_system } + /// Simulates writing to the platform clipboard pub fn write_to_clipboard(&self, item: ClipboardItem) { self.test_platform.write_to_clipboard(item) } + /// Simulates reading from the platform clipboard. + /// This will return the most recent value from `write_to_clipboard`. pub fn read_from_clipboard(&self) -> Option { self.test_platform.read_from_clipboard() } + /// Simulates choosing a File in the platform's "Open" dialog. pub fn simulate_new_path_selection( &self, select_path: impl FnOnce(&std::path::Path) -> Option, @@ -175,22 +195,27 @@ impl TestAppContext { self.test_platform.simulate_new_path_selection(select_path); } + /// Simulates clicking a button in an platform-level alert dialog. pub fn simulate_prompt_answer(&self, button_ix: usize) { self.test_platform.simulate_prompt_answer(button_ix); } + /// Returns true if there's an alert dialog open. pub fn has_pending_prompt(&self) -> bool { self.test_platform.has_pending_prompt() } + /// Simulates the user resizing the window to the new size. pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size) { self.test_window(window_handle).simulate_resize(size); } + /// Returns all windows open in the test. pub fn windows(&self) -> Vec { self.app.borrow().windows().clone() } + /// Run the given task on the main thread. pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 589493b4bb431586ed004e88e59d0d8f329334fc..4be1ffbf0fdf970353d6f2402eda02ab2b0faac8 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -32,6 +32,10 @@ pub struct ForegroundExecutor { not_send: PhantomData>, } +/// Task is a primitive that allows work to happen in the background. +/// It implements Future so you can `.await` on it. +/// If you drop a task it will be cancelled immediately. Calling `.detach()` allows +/// the task to continue running in the background, but with no way to return a value. #[must_use] #[derive(Debug)] pub enum Task { @@ -40,10 +44,12 @@ pub enum Task { } impl Task { + /// Create a new task that will resolve with the value pub fn ready(val: T) -> Self { Task::Ready(Some(val)) } + /// Detaching a task runs it to completion in the background pub fn detach(self) { match self { Task::Ready(_) => {} @@ -57,6 +63,8 @@ where T: 'static, E: 'static + Debug, { + /// Run the task to completion in the background and log any + /// errors that occur. #[track_caller] pub fn detach_and_log_err(self, cx: &mut AppContext) { let location = core::panic::Location::caller(); @@ -97,6 +105,10 @@ type AnyLocalFuture = Pin>>; type AnyFuture = Pin>>; +/// BackgroundExecutor lets you run things on background threads. +/// In production this is a thread pool with no ordering guarantees. +/// In tests this is simalated by running tasks one by one in a deterministic +/// (but arbitrary) order controlled by the `SEED` environment variable. impl BackgroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { dispatcher } @@ -135,6 +147,7 @@ impl BackgroundExecutor { Task::Spawned(task) } + /// Used by the test harness to run an async test in a syncronous fashion. #[cfg(any(test, feature = "test-support"))] #[track_caller] pub fn block_test(&self, future: impl Future) -> R { @@ -145,6 +158,8 @@ impl BackgroundExecutor { } } + /// Block the current thread until the given future resolves. + /// Consider using `block_with_timeout` instead. pub fn block(&self, future: impl Future) -> R { if let Ok(value) = self.block_internal(true, future, usize::MAX) { value @@ -206,6 +221,8 @@ impl BackgroundExecutor { } } + /// Block the current thread until the given future resolves + /// or `duration` has elapsed. pub fn block_with_timeout( &self, duration: Duration, @@ -238,6 +255,8 @@ impl BackgroundExecutor { } } + /// Scoped lets you start a number of tasks and waits + /// for all of them to complete before returning. pub async fn scoped<'scope, F>(&self, scheduler: F) where F: FnOnce(&mut Scope<'scope>), @@ -253,6 +272,9 @@ impl BackgroundExecutor { } } + /// Returns a task that will complete after the given duration. + /// Depending on other concurrent tasks the elapsed duration may be longer + /// than reqested. pub fn timer(&self, duration: Duration) -> Task<()> { let (runnable, task) = async_task::spawn(async move {}, { let dispatcher = self.dispatcher.clone(); @@ -262,65 +284,81 @@ impl BackgroundExecutor { Task::Spawned(task) } + /// in tests, start_waiting lets you indicate which task is waiting (for debugging only) #[cfg(any(test, feature = "test-support"))] pub fn start_waiting(&self) { self.dispatcher.as_test().unwrap().start_waiting(); } + /// in tests, removes the debugging data added by start_waiting #[cfg(any(test, feature = "test-support"))] pub fn finish_waiting(&self) { self.dispatcher.as_test().unwrap().finish_waiting(); } + /// in tests, run an arbitrary number of tasks (determined by the SEED environment variable) #[cfg(any(test, feature = "test-support"))] pub fn simulate_random_delay(&self) -> impl Future { self.dispatcher.as_test().unwrap().simulate_random_delay() } + /// in tests, indicate that a given task from `spawn_labeled` should run after everything else #[cfg(any(test, feature = "test-support"))] pub fn deprioritize(&self, task_label: TaskLabel) { self.dispatcher.as_test().unwrap().deprioritize(task_label) } + /// in tests, move time forward. This does not run any tasks, but does make `timer`s ready. #[cfg(any(test, feature = "test-support"))] pub fn advance_clock(&self, duration: Duration) { self.dispatcher.as_test().unwrap().advance_clock(duration) } + /// in tests, run one task. #[cfg(any(test, feature = "test-support"))] pub fn tick(&self) -> bool { self.dispatcher.as_test().unwrap().tick(false) } + /// in tests, run all tasks that are ready to run. If after doing so + /// the test still has outstanding tasks, this will panic. (See also `allow_parking`) #[cfg(any(test, feature = "test-support"))] pub fn run_until_parked(&self) { self.dispatcher.as_test().unwrap().run_until_parked() } + /// in tests, prevents `run_until_parked` from panicking if there are outstanding tasks. + /// This is useful when you are integrating other (non-GPUI) futures, like disk access, that + /// do take real async time to run. #[cfg(any(test, feature = "test-support"))] pub fn allow_parking(&self) { self.dispatcher.as_test().unwrap().allow_parking(); } + /// in tests, returns the rng used by the dispatcher and seeded by the `SEED` environment variable #[cfg(any(test, feature = "test-support"))] pub fn rng(&self) -> StdRng { self.dispatcher.as_test().unwrap().rng() } + /// How many CPUs are available to the dispatcher pub fn num_cpus(&self) -> usize { num_cpus::get() } + /// Whether we're on the main thread. pub fn is_main_thread(&self) -> bool { self.dispatcher.is_main_thread() } #[cfg(any(test, feature = "test-support"))] + /// in tests, control the number of ticks that `block_with_timeout` will run before timing out. pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive) { self.dispatcher.as_test().unwrap().set_block_on_ticks(range); } } +/// ForegroundExecutor runs things on the main thread. impl ForegroundExecutor { pub fn new(dispatcher: Arc) -> Self { Self { @@ -329,8 +367,7 @@ impl ForegroundExecutor { } } - /// Enqueues the given closure to be run on any thread. The closure returns - /// a future which will be run to completion on any available thread. + /// Enqueues the given Task to run on the main thread at some point in the future. pub fn spawn(&self, future: impl Future + 'static) -> Task where R: 'static, @@ -350,6 +387,7 @@ impl ForegroundExecutor { } } +/// Scope manages a set of tasks that are enqueued and waited on together. See `BackgroundExecutor#scoped` pub struct Scope<'a> { executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 5a21576fb26178ff67c750ca7ee63652690f1700..1771f29c67b13639fbb7b87029840c435b6a681d 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -1,3 +1,30 @@ +//! Test support for GPUI. +//! +//! GPUI provides first-class support for testing, which includes a macro to run test that rely on having a context, +//! and a test implementation of the `ForegroundExecutor` and `BackgroundExecutor` which ensure that your tests run +//! deterministically even in the face of arbitrary parallelism. +//! +//! The output of the `gpui::test` macro is understood by other rust test runners, so you can use it with `cargo test` +//! or `cargo-nextest`, or another runner of your choice. +//! +//! To make it possible to test collaborative user interfaces (like Zed) you can ask for as many different contexts +//! as you need. +//! +//! ## Example +//! +//! ``` +//! use gpui; +//! +//! #[gpui::test] +//! async fn test_example(cx: &TestAppContext) { +//! assert!(true) +//! } +//! +//! #[gpui::test] +//! async fn test_collaboration_example(cx_a: &TestAppContext, cx_b: &TestAppContext) { +//! assert!(true) +//! } +//! ``` use crate::{Entity, Subscription, TestAppContext, TestDispatcher}; use futures::StreamExt as _; use rand::prelude::*; @@ -68,6 +95,7 @@ impl futures::Stream for Observation { } } +/// observe returns a stream of the change events from the given `View` or `Model` pub fn observe(entity: &impl Entity, cx: &mut TestAppContext) -> Observation<()> { let (tx, rx) = smol::channel::unbounded(); let _subscription = cx.update(|cx| { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7e4c5f93f95e6ea770d404a63a3e6795d9a4be7d..dd567a83f6baee9ae65b57def9d7576b84bad89f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::{ px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, ArenaBox, ArenaRef, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, @@ -85,10 +87,12 @@ pub enum DispatchPhase { } impl DispatchPhase { + /// Returns true if this represents the "bubble" phase. pub fn bubble(self) -> bool { self == DispatchPhase::Bubble } + /// Returns true if this represents the "capture" phase. pub fn capture(self) -> bool { self == DispatchPhase::Capture } @@ -103,7 +107,10 @@ struct FocusEvent { current_focus_path: SmallVec<[FocusId; 8]>, } -slotmap::new_key_type! { pub struct FocusId; } +slotmap::new_key_type! { + /// A globally unique identifier for a focusable element. + pub struct FocusId; +} thread_local! { pub(crate) static ELEMENT_ARENA: RefCell = RefCell::new(Arena::new(4 * 1024 * 1024)); @@ -240,6 +247,7 @@ pub trait ManagedView: FocusableView + EventEmitter {} impl> ManagedView for M {} +/// Emitted by implementers of [ManagedView] to indicate the view should be dismissed, such as when a view is presented as a modal. pub struct DismissEvent; // Holds the state for a specific window. @@ -525,11 +533,13 @@ impl<'a> WindowContext<'a> { self.notify(); } + /// Blur the window and don't allow anything in it to be focused again. pub fn disable_focus(&mut self) { self.blur(); self.window.focus_enabled = false; } + /// Dispatch the given action on the currently focused element. pub fn dispatch_action(&mut self, action: Box) { let focus_handle = self.focused(); @@ -591,6 +601,9 @@ impl<'a> WindowContext<'a> { }); } + /// Subscribe to events emitted by a model or view. + /// The entity to which you're subscribing must implement the [EventEmitter] trait. + /// The callback will be invoked a handle to the emitting entity (either a [View] or [Model]), the event, and a window context for the current window. pub fn subscribe( &mut self, entity: &E, @@ -754,6 +767,9 @@ impl<'a> WindowContext<'a> { .request_measured_layout(style, rem_size, measure) } + /// Compute the layout for the given id within the given available space. + /// This method is called for its side effect, typically by the framework prior to painting. + /// After calling it, you can request the bounds of the given layout node id or any descendant. pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size) { let mut layout_engine = self.window.layout_engine.take().unwrap(); layout_engine.compute_layout(layout_id, available_space, self); @@ -788,30 +804,37 @@ impl<'a> WindowContext<'a> { .retain(&(), |callback| callback(self)); } + /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays. pub fn window_bounds(&self) -> WindowBounds { self.window.bounds } + /// Returns the size of the drawable area within the window. pub fn viewport_size(&self) -> Size { self.window.viewport_size } + /// Returns whether this window is focused by the operating system (receiving key events). pub fn is_window_active(&self) -> bool { self.window.active } + /// Toggle zoom on the window. pub fn zoom_window(&self) { self.window.platform_window.zoom(); } + /// Update the window's title at the platform level. pub fn set_window_title(&mut self, title: &str) { self.window.platform_window.set_title(title); } + /// Mark the window as dirty at the platform level. pub fn set_window_edited(&mut self, edited: bool) { self.window.platform_window.set_edited(edited); } + /// Determine the display on which the window is visible. pub fn display(&self) -> Option> { self.platform .displays() @@ -819,6 +842,7 @@ impl<'a> WindowContext<'a> { .find(|display| display.id() == self.window.display_id) } + /// Show the platform character palette. pub fn show_character_palette(&self) { self.window.platform_window.show_character_palette(); } @@ -936,6 +960,7 @@ impl<'a> WindowContext<'a> { .on_action(action_type, ArenaRef::from(listener)); } + /// Determine whether the given action is available along the dispatch path to the currently focused element. pub fn is_action_available(&self, action: &dyn Action) -> bool { let target = self .focused() @@ -962,6 +987,7 @@ impl<'a> WindowContext<'a> { self.window.modifiers } + /// Update the cursor style at the platform level. pub fn set_cursor_style(&mut self, style: CursorStyle) { self.window.requested_cursor_style = Some(style) } @@ -991,7 +1017,7 @@ impl<'a> WindowContext<'a> { true } - pub fn was_top_layer_under_active_drag( + pub(crate) fn was_top_layer_under_active_drag( &self, point: &Point, level: &StackingOrder, @@ -1649,6 +1675,7 @@ impl<'a> WindowContext<'a> { self.dispatch_keystroke_observers(event, None); } + /// Determine whether a potential multi-stroke key binding is in progress on this window. pub fn has_pending_keystrokes(&self) -> bool { self.window .rendered_frame @@ -1715,27 +1742,34 @@ impl<'a> WindowContext<'a> { subscription } + /// Focus the current window and bring it to the foreground at the platform level. pub fn activate_window(&self) { self.window.platform_window.activate(); } + /// Minimize the current window at the platform level. pub fn minimize_window(&self) { self.window.platform_window.minimize(); } + /// Toggle full screen status on the current window at the platform level. pub fn toggle_full_screen(&self) { self.window.platform_window.toggle_full_screen(); } + /// Present a platform dialog. + /// The provided message will be presented, along with buttons for each answer. + /// When a button is clicked, the returned Receiver will receive the index of the clicked button. pub fn prompt( &self, level: PromptLevel, - msg: &str, + message: &str, answers: &[&str], ) -> oneshot::Receiver { - self.window.platform_window.prompt(level, msg, answers) + self.window.platform_window.prompt(level, message, answers) } + /// Returns all available actions for the focused element. pub fn available_actions(&self) -> Vec> { let node_id = self .window @@ -1754,6 +1788,7 @@ impl<'a> WindowContext<'a> { .available_actions(node_id) } + /// Returns any key bindings that invoke the given action. pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { self.window .rendered_frame @@ -2279,6 +2314,10 @@ impl BorrowMut for WindowContext<'_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} +/// Provides access to application state that is specialized for a particular [View]. +/// Allows you to interact with focus, emit events, etc. +/// ViewContext also derefs to [WindowContext], giving you access to all of its methods as well. +/// When you call [View::::update], you're passed a `&mut V` and an `&mut ViewContext`. pub struct ViewContext<'a, V> { window_cx: WindowContext<'a>, view: &'a View, @@ -2316,14 +2355,17 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + /// Get the entity_id of this view. pub fn entity_id(&self) -> EntityId { self.view.entity_id() } + /// Get the view pointer underlying this context. pub fn view(&self) -> &View { self.view } + /// Get the model underlying this view. pub fn model(&self) -> &Model { &self.view.model } @@ -2333,6 +2375,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self.window_cx } + /// Set a given callback to be run on the next frame. pub fn on_next_frame(&mut self, f: impl FnOnce(&mut V, &mut ViewContext) + 'static) where V: 'static, @@ -2350,6 +2393,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Observe another model or view for changes to it's state, as tracked by the [AppContext::notify] pub fn observe( &mut self, entity: &E, @@ -2383,6 +2427,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Subscribe to events emitted by another model or view. + /// The entity to which you're subscribing must implement the [EventEmitter] trait. + /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [View] or [Model]), the event, and a view context for the current view. pub fn subscribe( &mut self, entity: &E, @@ -2440,6 +2487,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the given Model or View is released. pub fn observe_release( &mut self, entity: &E, @@ -2466,6 +2514,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty. + /// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn. pub fn notify(&mut self) { if !self.window.drawing { self.window_cx.notify(); @@ -2475,6 +2525,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } } + /// Register a callback to be invoked when the window is resized. pub fn observe_window_bounds( &mut self, mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, @@ -2488,6 +2539,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Register a callback to be invoked when the window is activated or deactivated. pub fn observe_window_activation( &mut self, mut callback: impl FnMut(&mut V, &mut ViewContext) + 'static, @@ -2620,6 +2672,10 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Schedule a future to be run asynchronously. + /// The given callback is invoked with a [WeakView] to avoid leaking the view for a long-running process. + /// It's also given an `AsyncWindowContext`, which can be used to access the state of the view across await points. + /// The returned future will be polled on the main thread. pub fn spawn( &mut self, f: impl FnOnce(WeakView, AsyncWindowContext) -> Fut, @@ -2632,6 +2688,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.window_cx.spawn(|cx| f(view, cx)) } + /// Update the global state of the given type. pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R where G: 'static, @@ -2642,6 +2699,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { result } + /// Register a callback to be invoked when the given global state changes. pub fn observe_global( &mut self, mut f: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static, @@ -2660,6 +2718,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } + /// Add a listener for any mouse event that occurs in the window. + /// This is a fairly low level method. + /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc. pub fn on_mouse_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, @@ -2672,6 +2733,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Register a callback to be invoked when the given Key Event is dispatched to the window. pub fn on_key_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, @@ -2684,6 +2746,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Register a callback to be invoked when the given Action type is dispatched to the window. pub fn on_action( &mut self, action_type: TypeId, @@ -2698,6 +2761,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Emit an event to be handled any other views that have subscribed via [ViewContext::subscribe]. pub fn emit(&mut self, event: Evt) where Evt: 'static, @@ -2711,6 +2775,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } + /// Move focus to the current view, assuming it implements [FocusableView]. pub fn focus_self(&mut self) where V: FocusableView, @@ -2718,6 +2783,11 @@ impl<'a, V: 'static> ViewContext<'a, V> { self.defer(|view, cx| view.focus_handle(cx).focus(cx)) } + /// Convenience method for accessing view state in an event callback. + /// + /// Many GPUI callbacks take the form of `Fn(&E, &mut WindowContext)`, + /// but it's often useful to be able to access view state in these + /// callbacks. This method provides a convenient way to do so. pub fn listener( &self, f: impl Fn(&mut V, &E, &mut ViewContext) + 'static, @@ -2827,14 +2897,20 @@ impl<'a, V> std::ops::DerefMut for ViewContext<'a, V> { } // #[derive(Clone, Copy, Eq, PartialEq, Hash)] -slotmap::new_key_type! { pub struct WindowId; } +slotmap::new_key_type! { + /// A unique identifier for a window. + pub struct WindowId; +} impl WindowId { + /// Converts this window ID to a `u64`. pub fn as_u64(&self) -> u64 { self.0.as_ffi() } } +/// A handle to a window with a specific root view type. +/// Note that this does not keep the window alive on its own. #[derive(Deref, DerefMut)] pub struct WindowHandle { #[deref] @@ -2844,6 +2920,8 @@ pub struct WindowHandle { } impl WindowHandle { + /// Create a new handle from a window ID. + /// This does not check if the root type of the window is `V`. pub fn new(id: WindowId) -> Self { WindowHandle { any_handle: AnyWindowHandle { @@ -2854,6 +2932,9 @@ impl WindowHandle { } } + /// Get the root view out of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn root(&self, cx: &mut C) -> Result> where C: Context, @@ -2865,6 +2946,9 @@ impl WindowHandle { })) } + /// Update the root view of this window. + /// + /// This will fail if the window has been closed or if the root view's type does not match pub fn update( &self, cx: &mut C, @@ -2881,6 +2965,9 @@ impl WindowHandle { })? } + /// Read the root view out of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> { let x = cx .windows @@ -2897,6 +2984,9 @@ impl WindowHandle { Ok(x.read(cx)) } + /// Read the root view out of this window, with a callback + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn read_with(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result where C: Context, @@ -2904,6 +2994,9 @@ impl WindowHandle { cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx)) } + /// Read the root view pointer off of this window. + /// + /// This will fail if the window is closed or if the root view's type does not match `V`. pub fn root_view(&self, cx: &C) -> Result> where C: Context, @@ -2911,6 +3004,9 @@ impl WindowHandle { cx.read_window(self, |root_view, _cx| root_view.clone()) } + /// Check if this window is 'active'. + /// + /// Will return `None` if the window is closed. pub fn is_active(&self, cx: &AppContext) -> Option { cx.windows .get(self.id) @@ -2946,6 +3042,7 @@ impl From> for AnyWindowHandle { } } +/// A handle to a window with any root view type, which can be downcast to a window with a specific root view type. #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct AnyWindowHandle { pub(crate) id: WindowId, @@ -2953,10 +3050,13 @@ pub struct AnyWindowHandle { } impl AnyWindowHandle { + /// Get the ID of this window. pub fn window_id(&self) -> WindowId { self.id } + /// Attempt to convert this handle to a window handle with a specific root view type. + /// If the types do not match, this will return `None`. pub fn downcast(&self) -> Option> { if TypeId::of::() == self.state_type { Some(WindowHandle { @@ -2968,6 +3068,9 @@ impl AnyWindowHandle { } } + /// Update the state of the root view of this window. + /// + /// This will fail if the window has been closed. pub fn update( self, cx: &mut C, @@ -2979,6 +3082,9 @@ impl AnyWindowHandle { cx.update_window(self, update) } + /// Read the state of the root view of this window. + /// + /// This will fail if the window has been closed. pub fn read(self, cx: &C, read: impl FnOnce(View, &AppContext) -> R) -> Result where C: Context, @@ -2999,6 +3105,10 @@ impl AnyWindowHandle { // } // } +/// An identifier for an [Element]. +/// +/// Can be constructed with a string, a number, or both, as well +/// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { View(EntityId), @@ -3074,7 +3184,8 @@ impl From<(&'static str, u64)> for ElementId { } } -/// A rectangle, to be rendered on the screen by GPUI at the given position and size. +/// A rectangle to be rendered in the window at the given position and size. +/// Passed as an argument [WindowContext::paint_quad]. #[derive(Clone)] pub struct PaintQuad { bounds: Bounds, From 53597620510d76d4070cd9f87ccb69b58e34b8a0 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:04:55 -0500 Subject: [PATCH 035/334] Document components/avatar Co-Authored-By: Antonio Scandurra --- crates/ui/src/components/avatar.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index b1dba69520c4d02ac735885f69d84db5d653236b..8e13bc9faa8f2e48227ab51757c847785043b51d 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -8,6 +8,16 @@ pub enum Shape { RoundedRectangle, } +/// An element that renders a user avatar with customizable appearance options. +/// +/// # Examples +/// +/// ``` +/// Avatar::new("path/to/image.png") +/// .shape(Shape::Circle) +/// .grayscale(true) +/// .border_color(cx.theme().colors().border) +/// ``` #[derive(IntoElement)] pub struct Avatar { image: Img, @@ -66,6 +76,16 @@ impl Avatar { } } + /// Sets the shape of the avatar image. + /// + /// This method allows the shape of the avatar to be specified using the [Shape] enum. + /// It modifies the corner radius of the image to match the specified shape. + /// + /// # Examples + /// + /// ``` + /// Avatar::new("path/to/image.png").shape(Shape::Circle); + /// ``` pub fn shape(mut self, shape: Shape) -> Self { self.image = match shape { Shape::Circle => self.image.rounded_full(), @@ -74,6 +94,13 @@ impl Avatar { self } + /// Applies a grayscale filter to the avatar image. + /// + /// # Examples + /// + /// ``` + /// let avatar = Avatar::new("path/to/image.png").grayscale(true); + /// ``` pub fn grayscale(mut self, grayscale: bool) -> Self { self.image = self.image.grayscale(grayscale); self From 458c672a720cdb106548afbd3423e234c0ec60a8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:16:34 -0700 Subject: [PATCH 036/334] Add more API docs Co-Authored-By: Conrad --- crates/gpui/src/app/test_context.rs | 35 +++++++++++++++++++++++++++-- crates/gpui/src/window.rs | 23 +++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 587d3f3fec4b5f6d278b4bd1e7272e5b6bf3d356..7c95f75d96cf1a1456971b122f9807783a80785c 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, @@ -255,6 +257,8 @@ impl TestAppContext { lock.update_global(update) } + /// Returns an `AsyncAppContext` which can be used to run tasks that expect to be on a background + /// thread on the current thread in tests. pub fn to_async(&self) -> AsyncAppContext { AsyncAppContext { app: Rc::downgrade(&self.app), @@ -263,6 +267,7 @@ impl TestAppContext { } } + /// Simulate dispatching an action to the currently focused node in the window. pub fn dispatch_action(&mut self, window: AnyWindowHandle, action: A) where A: Action, @@ -276,7 +281,8 @@ impl TestAppContext { /// simulate_keystrokes takes a space-separated list of keys to type. /// cx.simulate_keystrokes("cmd-shift-p b k s p enter") - /// will run backspace on the current editor through the command palette. + /// in Zed, this will run backspace on the current editor through the command palette. + /// This will also run the background executor until it's parked. pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) { for keystroke in keystrokes .split(" ") @@ -291,7 +297,8 @@ impl TestAppContext { /// simulate_input takes a string of text to type. /// cx.simulate_input("abc") - /// will type abc into your current editor. + /// will type abc into your current editor + /// This will also run the background executor until it's parked. pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) { for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) { self.dispatch_keystroke(window, keystroke.into(), false); @@ -300,6 +307,7 @@ impl TestAppContext { self.background_executor.run_until_parked() } + /// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`) pub fn dispatch_keystroke( &mut self, window: AnyWindowHandle, @@ -310,6 +318,7 @@ impl TestAppContext { .simulate_keystroke(keystroke, is_held) } + /// Returns the `TestWindow` backing the given handle. pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow { self.app .borrow_mut() @@ -324,6 +333,7 @@ impl TestAppContext { .clone() } + /// Returns a stream of notifications whenever the View or Model is updated. pub fn notifications(&mut self, entity: &impl Entity) -> impl Stream { let (tx, rx) = futures::channel::mpsc::unbounded(); self.update(|cx| { @@ -340,6 +350,7 @@ impl TestAppContext { rx } + /// Retuens a stream of events emitted by the given Model. pub fn events>( &mut self, entity: &Model, @@ -358,6 +369,8 @@ impl TestAppContext { rx } + /// Runs until the given condition becomes true. (Prefer `run_until_parked` if you + /// don't need to jump in at a specific time). pub async fn condition( &mut self, model: &Model, @@ -387,6 +400,7 @@ impl TestAppContext { } impl Model { + /// Block until the next event is emitted by the model, then return it. pub fn next_event(&self, cx: &mut TestAppContext) -> Evt where Evt: Send + Clone + 'static, @@ -416,6 +430,7 @@ impl Model { } impl View { + /// Returns a future that resolves when the view is next updated. pub fn next_notification(&self, cx: &TestAppContext) -> impl Future { use postage::prelude::{Sink as _, Stream as _}; @@ -442,6 +457,7 @@ impl View { } impl View { + /// Returns a future that resolves when the condition becomes true. pub fn condition( &self, cx: &TestAppContext, @@ -506,6 +522,8 @@ impl View { } } +/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to +/// run window-specific test code. use derive_more::{Deref, DerefMut}; #[derive(Deref, DerefMut, Clone)] pub struct VisualTestContext { @@ -520,6 +538,9 @@ impl<'a> VisualTestContext { self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() } + /// Create a new VisualTestContext. You would typically shadow the passed in + /// TestAppContext with this, as this is typically more useful. + /// `let cx = VisualTestContext::from_window(window, cx);` pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self { Self { cx: cx.clone(), @@ -527,10 +548,12 @@ impl<'a> VisualTestContext { } } + /// Wait until there are no more pending tasks. pub fn run_until_parked(&self) { self.cx.background_executor.run_until_parked(); } + /// Dispatch the action to the currently focused node. pub fn dispatch_action(&mut self, action: A) where A: Action, @@ -538,24 +561,32 @@ impl<'a> VisualTestContext { self.cx.dispatch_action(self.window, action) } + /// Read the title off the window (set by `WindowContext#set_window_title`) pub fn window_title(&mut self) -> Option { self.cx.test_window(self.window).0.lock().title.clone() } + /// Simulate a sequence of keystrokes `cx.simulate_keystrokes("cmd-p escape")` + /// Automatically runs until parked. pub fn simulate_keystrokes(&mut self, keystrokes: &str) { self.cx.simulate_keystrokes(self.window, keystrokes) } + /// Simulate typing text `cx.simulate_input("hello")` + /// Automatically runs until parked. pub fn simulate_input(&mut self, input: &str) { self.cx.simulate_input(self.window, input) } + /// Simulates the user blurring the window. pub fn deactivate_window(&mut self) { if Some(self.window) == self.test_platform.active_window() { self.test_platform.set_active_window(None) } self.background_executor.run_until_parked(); } + + /// Simulates the user closing the window. /// Returns true if the window was closed. pub fn simulate_close(&mut self) -> bool { let handler = self diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index dd567a83f6baee9ae65b57def9d7576b84bad89f..9b657a3f71c8fe226008d4ed56b787f17bc65a8c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -238,6 +238,7 @@ impl Drop for FocusHandle { /// FocusableView allows users of your view to easily /// focus it (using cx.focus_view(view)) pub trait FocusableView: 'static + Render { + /// Returns the focus handle associated with this view. fn focus_handle(&self, cx: &AppContext) -> FocusHandle; } @@ -251,6 +252,7 @@ impl> ManagedView for M {} pub struct DismissEvent; // Holds the state for a specific window. +#[doc(hidden)] pub struct Window { pub(crate) handle: AnyWindowHandle, pub(crate) removed: bool, @@ -442,6 +444,7 @@ impl Window { #[derive(Clone, Debug, Default, PartialEq, Eq)] #[repr(C)] pub struct ContentMask { + /// The bounds pub bounds: Bounds

, } @@ -1788,7 +1791,7 @@ impl<'a> WindowContext<'a> { .available_actions(node_id) } - /// Returns any key bindings that invoke the given action. + /// Returns key bindings that invoke the given action on the currently focused element. pub fn bindings_for_action(&self, action: &dyn Action) -> Vec { self.window .rendered_frame @@ -1799,6 +1802,7 @@ impl<'a> WindowContext<'a> { ) } + /// Returns any bindings that would invoke the given action on the given focus handle if it were focused. pub fn bindings_for_action_in( &self, action: &dyn Action, @@ -1817,6 +1821,7 @@ impl<'a> WindowContext<'a> { dispatch_tree.bindings_for_action(action, &context_stack) } + /// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle. pub fn listener_for( &self, view: &View, @@ -1828,6 +1833,7 @@ impl<'a> WindowContext<'a> { } } + /// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle. pub fn handler_for( &self, view: &View, @@ -1839,7 +1845,8 @@ impl<'a> WindowContext<'a> { } } - //========== ELEMENT RELATED FUNCTIONS =========== + /// Invoke the given function with the given focus handle present on the key dispatch stack. + /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint. pub fn with_key_dispatch( &mut self, context: Option, @@ -1878,6 +1885,8 @@ impl<'a> WindowContext<'a> { } } + /// Register a callback that can interrupt the closing of the current window based the returned boolean. + /// If the callback returns false, the window won't be closed. pub fn on_window_should_close(&mut self, f: impl Fn(&mut WindowContext) -> bool + 'static) { let mut this = self.to_async(); self.window @@ -2052,19 +2061,24 @@ impl<'a> BorrowMut for WindowContext<'a> { } } +/// This trait contains functionality that is shared across [ViewContext] and [WindowContext] pub trait BorrowWindow: BorrowMut + BorrowMut { + #[doc(hidden)] fn app_mut(&mut self) -> &mut AppContext { self.borrow_mut() } + #[doc(hidden)] fn app(&self) -> &AppContext { self.borrow() } + #[doc(hidden)] fn window(&self) -> &Window { self.borrow() } + #[doc(hidden)] fn window_mut(&mut self) -> &mut Window { self.borrow_mut() } @@ -3111,10 +3125,15 @@ impl AnyWindowHandle { /// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { + /// The id of a View element View(EntityId), + /// An integer id Integer(usize), + /// A string based id Name(SharedString), + /// An id that's equated with a focus handle FocusHandle(FocusId), + /// A combination of a name and an integer NamedInteger(SharedString, usize), } From 324fd24709b3b004d4fe3c821f0ad4b1a4723393 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:39:15 -0500 Subject: [PATCH 037/334] Initial button documentation Co-Authored-By: Antonio Scandurra --- crates/ui/src/components/button/button.rs | 169 ++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 398f8f10e27831a07bdca21f837e1425f396c5e9..d3d3326544046eebf6040a222100097f3cf4758f 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -7,6 +7,63 @@ use crate::{ use super::button_icon::ButtonIcon; +/// [Button] can be use to create a button with a label and an optional icon. +/// +/// Common buttons: +/// - Label, Icon + Label: [Button] (this component) +/// - Icon only: [IconButton] +/// - Custom: [ButtonLike] +/// +/// To create a more complex button than what the [Button] or [IconButton] components provide, use +/// [ButtonLike] directly. +/// +/// # Examples +/// +/// A button with a label only: +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +/// **A toggleable button**, is typically used in scenarios such as a toolbar, +/// where the button's state indicates whether a feature is enabled or not, or +/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu. +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .icon(IconName::Check) +/// .selected(some_bool) +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +/// To change the style of the button when it is selected use the [selected_style][Button::selected_style] method. +/// +/// ``` +/// Button::new("button_id", "Click me!") +/// .selected(some_bool) +/// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// This will create a button with a blue tinted background when selected. +/// +/// **A full-width button**, is typically used in scenarios such as the bottom of a modal or form, where it occupies the entire width of its container. +/// The button's content, including text and icons, is centered by default. +/// +/// ``` +/// let button = Button::new("button_id", "Click me!") +/// .full_width() +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// #[derive(IntoElement)] pub struct Button { base: ButtonLike, @@ -23,6 +80,12 @@ pub struct Button { } impl Button { + /// Creates a new [Button] with a specified identifier and label. + /// + /// This is the primary constructor for a `Button` component. It initializes + /// the button with the provided identifier and label text, setting all other + /// properties to their default values, which can be customized using the + /// builder pattern methods provided by this struct. pub fn new(id: impl Into, label: impl Into) -> Self { Self { base: ButtonLike::new(id), @@ -39,46 +102,55 @@ impl Button { } } + /// Sets the color of the button's label. pub fn color(mut self, label_color: impl Into>) -> Self { self.label_color = label_color.into(); self } + /// Defines the size of the button's label. pub fn label_size(mut self, label_size: impl Into>) -> Self { self.label_size = label_size.into(); self } + /// Sets the label used when the button is in a selected state. pub fn selected_label>(mut self, label: impl Into>) -> Self { self.selected_label = label.into().map(Into::into); self } + /// Assigns an icon to the button. pub fn icon(mut self, icon: impl Into>) -> Self { self.icon = icon.into(); self } + /// Sets the position of the icon relative to the label. pub fn icon_position(mut self, icon_position: impl Into>) -> Self { self.icon_position = icon_position.into(); self } + /// Specifies the size of the button's icon. pub fn icon_size(mut self, icon_size: impl Into>) -> Self { self.icon_size = icon_size.into(); self } + /// Sets the color of the button's icon. pub fn icon_color(mut self, icon_color: impl Into>) -> Self { self.icon_color = icon_color.into(); self } + /// Chooses an icon to display when the button is in a selected state. pub fn selected_icon(mut self, icon: impl Into>) -> Self { self.selected_icon = icon.into(); self } + /// Binds a key combination to the button for keyboard shortcuts. pub fn key_binding(mut self, key_binding: impl Into>) -> Self { self.key_binding = key_binding.into(); self @@ -86,6 +158,22 @@ impl Button { } impl Selectable for Button { + /// Sets the selected state of the button. + /// + /// This method allows the selection state of the button to be specified. + /// It modifies the button's appearance to reflect its selected state. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .selected(true) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// Use [selected_style](Button::selected_style) to change the style of the button when it is selected. fn selected(mut self, selected: bool) -> Self { self.base = self.base.selected(selected); self @@ -93,6 +181,19 @@ impl Selectable for Button { } impl SelectableButton for Button { + /// Sets the style for the button when selected. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .selected(true) + /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// This results in a button with a blue tinted background when selected. fn selected_style(mut self, style: ButtonStyle) -> Self { self.base = self.base.selected_style(style); self @@ -100,6 +201,22 @@ impl SelectableButton for Button { } impl Disableable for Button { + /// Disables the button. + /// + /// This method allows the button to be disabled. When a button is disabled, + /// it doesn't react to user interactions and its appearance is updated to reflect this. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .disabled(true) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This results in a button that is disabled and does not respond to click events. fn disabled(mut self, disabled: bool) -> Self { self.base = self.base.disabled(disabled); self @@ -107,6 +224,7 @@ impl Disableable for Button { } impl Clickable for Button { + /// Sets the click event handler for the button. fn on_click( mut self, handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static, @@ -117,11 +235,40 @@ impl Clickable for Button { } impl FixedWidth for Button { + /// Sets a fixed width for the button. + /// + /// This function allows a button to have a fixed width instead of automatically growing or shrinking. + /// Sets a fixed width for the button. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .width(DefiniteLength::Pixels(100)) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This sets the button's width to be exactly 100 pixels. fn width(mut self, width: DefiniteLength) -> Self { self.base = self.base.width(width); self } + /// Sets the button to occupy the full width of its container. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .full_width() + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This stretches the button to the full width of its container. fn full_width(mut self) -> Self { self.base = self.base.full_width(); self @@ -129,20 +276,42 @@ impl FixedWidth for Button { } impl ButtonCommon for Button { + /// Sets the button's id. fn id(&self) -> &ElementId { self.base.id() } + /// Sets the visual style of the button using a [ButtonStyle]. fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } + /// Sets the button's size using a [ButtonSize]. fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self } + /// Sets a tooltip for the button. + /// + /// This method allows a tooltip to be set for the button. The tooltip is a function that + /// takes a mutable reference to a [WindowContext] and returns an [AnyView]. The tooltip + /// is displayed when the user hovers over the button. + /// + /// # Examples + /// + /// ``` + /// Button::new("button_id", "Click me!") + /// .tooltip(|cx| { + /// Text::new("This is a tooltip").into() + /// }) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over. fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { self.base = self.base.tooltip(tooltip); self From 5f1513893398b4b0ed084feb0f32dfeefe3b2e08 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 9 Jan 2024 13:41:42 -0500 Subject: [PATCH 038/334] WIP Co-Authored-By: Julia <30666851+ForLoveOfCats@users.noreply.github.com> --- CONTRIBUTING.md | 79 +++++++++++-------------------------------------- 1 file changed, 17 insertions(+), 62 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41261250f32005ef669c8b3e4ba830f4fa96a19c..825ddce79d2611bf1e48f4e2fcc11bdc98beeb6b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,26 +2,26 @@ Thanks for your interest in contributing to Zed, the collaborative platform that is also a code editor! +We want to ensure that no one ends up spending time on a pull request that may not be accepted, so we ask that you discuss your ideas with the team and community before starting on a contribution. + +All activity in Zed communities is subject to our [Code of Conduct](https://docs.zed.dev/community/code-of-conduct). Contributors to Zed must sign our [Contributor License Agreement TODO](LINK) before their contributions can be merged. + ## Contribution ideas If you already have an idea of what you'd like to contribute, you can skip this section, otherwise, here are a few resources to help you find something to work on: -- Our public roadmap [include link] details what features we plan to add to Zed. +- Our [public roadmap TODO](LINK) details what features we plan to add to Zed. - Our [Top-Ranking Issues issue](https://github.com/zed-industries/community/issues/52) shows the most popular feature requests and issues, as voted on by the community. -*If you are a plugin developer looking to contribute by building out the Zed ecosystem, have a look at these [issues](https://github.com/zed-industries/community/issues?q=is%3Aopen+is%3Aissue+label%3A%22potential+plugin%22+sort%3Areactions-%2B1-desc).* - -In the short term, we want to provide a generalized solutions to these problems (plugin system/theme system), so we are not looking to add these features to Zed itself - -- Adding languages -- Themes +At the moment, we are generally not looking to extend Zed's language or theme support by directly adding these features to Zed - we really want to build a plugin system to handle making the editor extensible going forward. This isn't to say that we won't accept contributions that add support for a new language or theme, but more to emphasize that we want to discuss these types of contributions first. ## Resources ### Bird-eye's view of Zed Zed is made up of several smaller crates - let's go over those you're most likely to interact with: -- [gpui](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. + +- [gpui](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation** - [editor](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions. - [project](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP. - [workspace](/crates/workspace) handles local state serialization and groups projects together. @@ -31,29 +31,9 @@ Zed is made up of several smaller crates - let's go over those you're most likel - [collab](/crates/collab) is the collaboration server itself, driving the collaboration features such as project sharing. - [rpc](/crates/rpc) defines messages to be exchanged with collaboration server. -// Let's try to make whoever we come into contact with for the first time -// well-equiped to discuss basic concepts around Zed -// Ideally these should link to mdbook/source code docs (doubtful given how source code docs might be more in-depth than necessary) -### Important concepts - -- Views vs Models -- Contexts -- Action -- UI - - Render vs RenderOnce - - ui crate - - storybook -- Workspace -- Project - - Worktree -- vim crate - - Editor - - Multibuffers -- Settings - ## Zed channels -Once you have an idea of what you'd like to contribute, you'll want to communicate this to the team. If you're new to Zed's channels, here's a guide [link to up-to-date docs] to help bring you up to speed. +Once you have an idea of what you'd like to contribute, you'll want to communicate this to the team. If you're new to Zed's channels, here's a guide [link to up-to-date docs TODO](LINK) to help bring you up to speed. [Since ~February 2022, the Zed Industries team has been exclusively using Zed to build Zed](https://x.com/nathansobo/status/1497958891509932035). We've built these tools to specifically address our own issues and frustrations with the current state of collaborative coding. These are not features we've built to simply look flashy, we work in channels every day of the workweek, aggressively dogfooding everything. @@ -63,43 +43,18 @@ Once you have an idea of what you'd like to contribute, you'll want to communica While we still have improvements to make, we believe we've sanded down a lot of the sharp edges and that the experience is both smooth and enjoyable - one that gets you as close to hypothetically sitting next to your teammates as possible, even if you're potentially on different sides of the globe. We want to continue working this way amongst ourselves and we are extremely excited to work with *you* in this way. We invite you to contribute to Zed *through* Zed. -### Proposal & Discussion - -To do that, find a public channel [link to list of all public channels] that is relevant to your contribution, check the channel notes to see which Zed team members typically work in that channel, and post a message in the chat. If you're not sure which channel is best, you can post in the channel. *Please wait to begin working on your contribution until you've received feedback from the team. Turning down a contribution that was not discussed beforehand is a bummer for everyone.* - -## Implementation & Help - -Once approved, feel free to begin working on your contribution. If you have any questions, you can post in the channel you originally proposed your contribution in, or you can post in the channel. If you need help, reach out to a Zed teammate - we're happy to pair with you to help you learn the codebase and get your contribution merged. - -Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming. We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed. - ---- Piotr's original contribution guide --- +We plan to organize office hours on a weekly basis - they will take place in forelinked Zed channel. -Read on if you're looking for an outline of your first contribution - from finding your way around the codebase and asking questions, through modifying and testing the changes, finishing off with submitting your changes for review and interacting with Zed core team and Zed community as a whole. - -### Getting in touch -We believe that journeys are best when shared - hence there are multiple outlets for Zed users and developers to share their success stories and hurdles. +### Proposal & Discussion -If you have questions, ask them away on our [Discord](https://discord.gg/XTtXmZYEpN) or in a dedicated [Zed channel](https://zed.dev/preview/channel/open-source-81). We also plan to organise office hours on a weekly basis - they will take place in forelinked Zed channel. +Before starting on a contribution, we ask that you look to see if there is any existing PRs, or in-Zed discussions about the thing you want to implement. If there is no existing work, find a [public channel TODO](LINK) that is relevant to your contribution, check the channel notes to see which Zed team members typically work in that channel, and post a message in the chat. If you're not sure which channel is best, you can post in the channel. -All activity in Zed communities is subject to our [Code of Conduct](https://docs.zed.dev/community/code-of-conduct). +*Please wait to begin working on your contribution until you've received feedback from the team. Turning down a contribution that was not discussed beforehand is a bummer for everyone.* +## Implementation & Help -If you're just starting out with Zed, it might be worthwhile to look at some of the other crates that implement bits of UI - such as [go to line](/crates/go_to_line) modal that's bound to ctrl-g by default in Zed. +Once approved, feel free to begin working on your contribution. If you have any questions, you can post in the channel you originally proposed your contribution in, or you can post in the channel. If you need help, reach out to a Zed teammate - we're happy to pair with you to help you learn the codebase and get your contribution merged. -### Upstreaming your changes -Here be dragons :) ---- -Other things to mention here -- [ ] Etiquette -- [ ] CLA -- [ ] Importance of tests -- [ ] Look over Piotr's PR and pull in what this is missing (tour of the codebase, etc.) - - See above -- [ ] Ask people to check the PRs to see if something has already been started on -- [ ] Maybe have a channel that maps out what teammates / community users are working on what, so people can see what's being worked on -- [ ] Mention Discord or keep it only focused on Zed channels? -- [ ] Mention issue triage doc (https://github.com/zed-industries/community/blob/main/processes/issues_triage.md)? +**Zed makes heavy use of unit and integration testing, we encourage you to write tests for your contribution.** -Things to do: -- [ ] Put names devs who "own" each channel in the channel notes +Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming. We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed. From 7dbe0519ece2b472ef3ebeee348bb2bd268300a9 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:49:27 -0500 Subject: [PATCH 039/334] Add label docs --- crates/ui/src/components/button/button.rs | 5 +- crates/ui/src/components/label/label.rs | 58 ++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index d3d3326544046eebf6040a222100097f3cf4758f..b65bf050eb87c3055c1c4eb923ff2c9f2b9c4ee9 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -7,7 +7,7 @@ use crate::{ use super::button_icon::ButtonIcon; -/// [Button] can be use to create a button with a label and an optional icon. +/// An element that creates a button with a label and an optional icon. /// /// Common buttons: /// - Label, Icon + Label: [Button] (this component) @@ -19,7 +19,8 @@ use super::button_icon::ButtonIcon; /// /// # Examples /// -/// A button with a label only: +/// **A button with a label**, is typically used in scenarios such as a form, where the button's label +/// indicates what action will be performed when the button is clicked. /// /// ``` /// Button::new("button_id", "Click me!") diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 61f463a531b85dc9a2780a334c8a771ab5c9f40b..7f092883ac6ae48f9eb0724783a3434ad16b14e0 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -2,6 +2,28 @@ use gpui::WindowContext; use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; +/// A struct representing a label element in the UI. +/// +/// The `Label` struct stores the label text and common properties for a label element. +/// It provides methods for modifying these properties. +/// +/// # Examples +/// +/// ``` +/// Label::new("Hello, World!") +/// ``` +/// +/// **A colored label**, for example labeling a dangerous action: +/// +/// ``` +/// let my_label = Label::new("Delete").color(Color::Error); +/// ``` +/// +/// **A label with a strikethrough**, for example labeling something that has been deleted: +/// +/// ``` +/// let my_label = Label::new("Deleted").strikethrough(true); +/// ``` #[derive(IntoElement)] pub struct Label { base: LabelLike, @@ -9,6 +31,13 @@ pub struct Label { } impl Label { + /// Create a new `Label` with the given text. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!"); + /// ``` pub fn new(label: impl Into) -> Self { Self { base: LabelLike::new(), @@ -18,26 +47,53 @@ impl Label { } impl LabelCommon for Label { + /// Sets the size of the label using a [LabelSize]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").size(LabelSize::Large); + /// ``` fn size(mut self, size: LabelSize) -> Self { self.base = self.base.size(size); self } + /// Sets the line height style of the label using a [LineHeightStyle]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::Normal); + /// ``` fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { self.base = self.base.line_height_style(line_height_style); self } + /// Sets the color of the label using a [Color]. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").color(Color::Primary); + /// ``` fn color(mut self, color: Color) -> Self { self.base = self.base.color(color); self } + /// Sets the strikethrough property of the label. + /// + /// # Examples + /// + /// ``` + /// let my_label = Label::new("Hello, World!").strikethrough(true); + /// ``` fn strikethrough(mut self, strikethrough: bool) -> Self { self.base = self.base.strikethrough(strikethrough); self } -} impl RenderOnce for Label { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { From 46065c26210c19f9213edee8b736045c41df2142 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 13:52:11 -0500 Subject: [PATCH 040/334] Fix unclosed delimiter --- crates/ui/src/components/label/label.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 7f092883ac6ae48f9eb0724783a3434ad16b14e0..8cc43bc99374432188a456e5fb2b6af12d2eb092 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -94,6 +94,7 @@ impl LabelCommon for Label { self.base = self.base.strikethrough(strikethrough); self } +} impl RenderOnce for Label { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { From 42cbd103fb91f80a66fdbee1166f72295541b0e1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 9 Jan 2024 11:46:43 -0700 Subject: [PATCH 041/334] Even more docs Co-authored-by: Conrad --- crates/editor/src/display_map.rs | 1 - crates/gpui/src/app.rs | 37 ++++++++++++++++------- crates/gpui/src/app/async_context.rs | 6 ++++ crates/gpui/src/app/test_context.rs | 26 ++++++++++++---- crates/gpui/src/platform/test/platform.rs | 1 + crates/gpui_macros/src/gpui_macros.rs | 30 ++++++++++++++++++ 6 files changed, 83 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4511ffe407849162b603c4b3a44d51e8d552c1c9..4f2d5179dbd08fedd38f46ea23f919d6c30147c8 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1015,7 +1015,6 @@ pub mod tests { .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); - let _test_platform = &cx.test_platform; let mut tab_size = rng.gen_range(1..=4); let buffer_start_excerpt_header_height = rng.gen_range(1..=5); let excerpt_header_height = rng.gen_range(1..=5); diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 638396abc51e97f58a93fabec816bd8d48a50135..f84ae809d34b14712495de95043e68ae28fe88a3 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -45,11 +45,13 @@ use util::{ /// Temporary(?) wrapper around [`RefCell`] to help us debug any double borrows. /// Strongly consider removing after stabilization. +#[doc(hidden)] pub struct AppCell { app: RefCell, } impl AppCell { + #[doc(hidden)] #[track_caller] pub fn borrow(&self) -> AppRef { if option_env!("TRACK_THREAD_BORROWS").is_some() { @@ -59,6 +61,7 @@ impl AppCell { AppRef(self.app.borrow()) } + #[doc(hidden)] #[track_caller] pub fn borrow_mut(&self) -> AppRefMut { if option_env!("TRACK_THREAD_BORROWS").is_some() { @@ -69,6 +72,7 @@ impl AppCell { } } +#[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRef<'a>(Ref<'a, AppContext>); @@ -81,6 +85,7 @@ impl<'a> Drop for AppRef<'a> { } } +#[doc(hidden)] #[derive(Deref, DerefMut)] pub struct AppRefMut<'a>(RefMut<'a, AppContext>); @@ -93,6 +98,8 @@ impl<'a> Drop for AppRefMut<'a> { } } +/// A reference to a GPUI application, typically constructed in the `main` function of your app. +/// You won't interact with this type much outside of initial configuration and startup. pub struct App(Rc); /// Represents an application before it is fully launched. Once your app is @@ -136,6 +143,8 @@ impl App { self } + /// Invokes a handler when an already-running application is launched. + /// On macOS, this can occur when the application icon is double-clicked or the app is launched via the dock. pub fn on_reopen(&self, mut callback: F) -> &Self where F: 'static + FnMut(&mut AppContext), @@ -149,18 +158,22 @@ impl App { self } + /// Returns metadata associated with the application pub fn metadata(&self) -> AppMetadata { self.0.borrow().app_metadata.clone() } + /// Returns a handle to the [BackgroundExecutor] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } + /// Returns a handle to the [ForegroundExecutor] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } + /// Returns a reference to the [TextSystem] associated with this app. pub fn text_system(&self) -> Arc { self.0.borrow().text_system.clone() } @@ -174,12 +187,6 @@ type QuitHandler = Box LocalBoxFuture<'static, () type ReleaseListener = Box; type NewViewListener = Box; -// struct FrameConsumer { -// next_frame_callbacks: Vec, -// task: Task<()>, -// display_linker -// } - pub struct AppContext { pub(crate) this: Weak, pub(crate) platform: Rc, @@ -314,10 +321,12 @@ impl AppContext { } } + /// Gracefully quit the application via the platform's standard routine. pub fn quit(&mut self) { self.platform.quit(); } + /// Get metadata about the app and platform. pub fn app_metadata(&self) -> AppMetadata { self.app_metadata.clone() } @@ -340,6 +349,7 @@ impl AppContext { result } + /// Arrange a callback to be invoked when the given model or view calls `notify` on its respective context. pub fn observe( &mut self, entity: &E, @@ -355,7 +365,7 @@ impl AppContext { }) } - pub fn observe_internal( + pub(crate) fn observe_internal( &mut self, entity: &E, mut on_notify: impl FnMut(E, &mut AppContext) -> bool + 'static, @@ -380,15 +390,17 @@ impl AppContext { subscription } - pub fn subscribe( + /// Arrange for the given callback to be invoked whenever the given model or view emits an event of a given type. + /// The callback is provided a handle to the emitting entity and a reference to the emitted event. + pub fn subscribe( &mut self, entity: &E, - mut on_event: impl FnMut(E, &Evt, &mut AppContext) + 'static, + mut on_event: impl FnMut(E, &Event, &mut AppContext) + 'static, ) -> Subscription where - T: 'static + EventEmitter, + T: 'static + EventEmitter, E: Entity, - Evt: 'static, + Event: 'static, { self.subscribe_internal(entity, move |entity, event, cx| { on_event(entity, event, cx); @@ -426,6 +438,9 @@ impl AppContext { subscription } + /// Returns handles to all open windows in the application. + /// Each handle could be downcast to a handle typed for the root view of that window. + /// To find all windows of a given type, you could filter on pub fn windows(&self) -> Vec { self.windows .values() diff --git a/crates/gpui/src/app/async_context.rs b/crates/gpui/src/app/async_context.rs index 475ef76ef25cec97cc75b30953f9ad30ce87a5cd..6afb356e5e63c6431fe0858b4e1c4fd97159c0b2 100644 --- a/crates/gpui/src/app/async_context.rs +++ b/crates/gpui/src/app/async_context.rs @@ -82,6 +82,7 @@ impl Context for AsyncAppContext { } impl AsyncAppContext { + /// Schedules all windows in the application to be redrawn. pub fn refresh(&mut self) -> Result<()> { let app = self .app @@ -92,14 +93,17 @@ impl AsyncAppContext { Ok(()) } + /// Get an executor which can be used to spawn futures in the background. pub fn background_executor(&self) -> &BackgroundExecutor { &self.background_executor } + /// Get an executor which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> &ForegroundExecutor { &self.foreground_executor } + /// Invoke the given function in the context of the app, then flush any effects produced during its invocation. pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { let app = self .app @@ -109,6 +113,7 @@ impl AsyncAppContext { Ok(f(&mut lock)) } + /// Open a window with the given options based on the root view returned by the given function. pub fn open_window( &self, options: crate::WindowOptions, @@ -125,6 +130,7 @@ impl AsyncAppContext { Ok(lock.open_window(options, build_root_view)) } + /// Schedule a future to be polled in the background. pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 7c95f75d96cf1a1456971b122f9807783a80785c..de31339b8d79bb3b4a80d4e8c9bc95834ffef92c 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -15,11 +15,15 @@ use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; /// an implementation of `Context` with additional methods that are useful in tests. #[derive(Clone)] pub struct TestAppContext { + #[doc(hidden)] pub app: Rc, + #[doc(hidden)] pub background_executor: BackgroundExecutor, + #[doc(hidden)] pub foreground_executor: ForegroundExecutor, + #[doc(hidden)] pub dispatcher: TestDispatcher, - pub test_platform: Rc, + test_platform: Rc, text_system: Arc, } @@ -80,6 +84,7 @@ impl Context for TestAppContext { } impl TestAppContext { + /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you. pub fn new(dispatcher: TestDispatcher) -> Self { let arc_dispatcher = Arc::new(dispatcher.clone()); let background_executor = BackgroundExecutor::new(arc_dispatcher.clone()); @@ -138,8 +143,8 @@ impl TestAppContext { f(&*cx) } - // Adds a new window. The Window will always be backed by a `TestWindow` which - // can be retrieved with `self.test_window(handle)` + /// Adds a new window. The Window will always be backed by a `TestWindow` which + /// can be retrieved with `self.test_window(handle)` pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, @@ -149,7 +154,7 @@ impl TestAppContext { cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window)) } - // Adds a new window with no content. + /// Adds a new window with no content. pub fn add_empty_window(&mut self) -> AnyWindowHandle { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) @@ -226,16 +231,20 @@ impl TestAppContext { self.foreground_executor.spawn(f(self.to_async())) } + /// true if the given global is defined pub fn has_global(&self) -> bool { let app = self.app.borrow(); app.has_global::() } + /// runs the given closure with a reference to the global + /// panics if `has_global` would return false. pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> R { let app = self.app.borrow(); read(app.global(), &app) } + /// runs the given closure with a reference to the global (if set) pub fn try_read_global( &self, read: impl FnOnce(&G, &AppContext) -> R, @@ -244,11 +253,13 @@ impl TestAppContext { Some(read(lock.try_global()?, &lock)) } + /// sets the global in this context. pub fn set_global(&mut self, global: G) { let mut lock = self.app.borrow_mut(); lock.set_global(global); } + /// updates the global in this context. (panics if `has_global` would return false) pub fn update_global( &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, @@ -522,10 +533,10 @@ impl View { } } -/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to -/// run window-specific test code. use derive_more::{Deref, DerefMut}; #[derive(Deref, DerefMut, Clone)] +/// A VisualTestContext is the test-equivalent of a `WindowContext`. It allows you to +/// run window-specific test code. pub struct VisualTestContext { #[deref] #[deref_mut] @@ -534,6 +545,7 @@ pub struct VisualTestContext { } impl<'a> VisualTestContext { + /// Provides the `WindowContext` for the duration of the closure. pub fn update(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R { self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() } @@ -723,6 +735,7 @@ impl VisualContext for VisualTestContext { } impl AnyWindowHandle { + /// Creates the given view in this window. pub fn build_view( &self, cx: &mut TestAppContext, @@ -732,6 +745,7 @@ impl AnyWindowHandle { } } +/// An EmptyView for testing. pub struct EmptyView {} impl Render for EmptyView { diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index ec3d7a0ff008ee64c92e8ceece0a2ef32cacff14..c4452a593a3747fd10534b75c7f0622479585075 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -15,6 +15,7 @@ use std::{ time::Duration, }; +/// TestPlatform implements the Platform trait for use in tests. pub struct TestPlatform { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index f0cd59908dc6967d3b00e1b0991d2390b61a59e1..1187d96ca320abbc51b66b24b0639b1211b451dc 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -7,26 +7,56 @@ mod test; use proc_macro::TokenStream; #[proc_macro] +/// register_action! can be used to register an action with the GPUI runtime. +/// You should typically use `gpui::actions!` or `gpui::impl_actions!` instead, +/// but this can be used for fine grained customization. pub fn register_action(ident: TokenStream) -> TokenStream { register_action::register_action_macro(ident) } #[proc_macro_derive(IntoElement)] +// #[derive(IntoElement)] is used to create a Component out of anything that implements +// the `RenderOnce` trait. pub fn derive_into_element(input: TokenStream) -> TokenStream { derive_into_element::derive_into_element(input) } #[proc_macro_derive(Render)] +#[doc(hidden)] pub fn derive_render(input: TokenStream) -> TokenStream { derive_render::derive_render(input) } +// Used by gpui to generate the style helpers. #[proc_macro] +#[doc(hidden)] pub fn style_helpers(input: TokenStream) -> TokenStream { style_helpers::style_helpers(input) } #[proc_macro_attribute] +/// #[gpui::test] can be used to annotate test functions that run with GPUI support. +/// it supports both synchronous and asynchronous tests, and can provide you with +/// as many `TestAppContext` instances as you need. +/// The output contains a `#[test]` annotation so this can be used with any existing +/// test harness (`cargo test` or `cargo-nextest`). +/// +/// ``` +/// #[gpui::test] +/// async fn test_foo(mut cx: &TestAppContext) { } +/// ``` +/// +/// In addition to passing a TestAppContext, you can also ask for a `StdRnd` instance. +/// this will be seeded with the `SEED` environment variable and is used internally by +/// the ForegroundExecutor and BackgroundExecutor to run tasks deterministically in tests. +/// Using the same `StdRng` for behaviour in your test will allow you to exercise a wide +/// variety of scenarios and interleavings just by changing the seed. +/// +/// #[gpui::test] also takes three different arguments: +/// - `#[gpui::test(interations=10)]` will run the test ten times with a different initial SEED. +/// - `#[gpui::test(retries=3)]` will run the test up to four times if it fails to try and make it pass. +/// - `#[gpui::test(on_failure="crate::test::report_failure")]` will call the specified function after the +/// tests fail so that you can write out more detail about the failure. pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { test::test(args, function) } From e020d7ca110cd361af30a59225bde5f4b10be3d8 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:15:25 -0500 Subject: [PATCH 042/334] Document ui crate traits --- crates/ui/src/clickable.rs | 2 +- crates/ui/src/components/button/button_like.rs | 2 ++ crates/ui/src/components/label/label_like.rs | 1 + crates/ui/src/disableable.rs | 2 +- crates/ui/src/fixed.rs | 2 +- crates/ui/src/selectable.rs | 9 ++++++++- crates/ui/src/styled_ext.rs | 8 +++++++- 7 files changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/ui/src/clickable.rs b/crates/ui/src/clickable.rs index 44f40b4cd4ccb98244a369b70639e3a768f4ddfa..462d2c60998a993527fc5dab24d1b0a2ed260d86 100644 --- a/crates/ui/src/clickable.rs +++ b/crates/ui/src/clickable.rs @@ -1,6 +1,6 @@ use gpui::{ClickEvent, WindowContext}; -/// A trait for elements that can be clicked. +/// A trait for elements that can be clicked. Enables the use of the `on_click` method. pub trait Clickable { /// Sets the click handler that will fire whenever the element is clicked. fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self; diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 431286073fb33b263fd08c27ba1c07e44fe82c01..d6ecad41fc657860ba31622810e8459cb4a72072 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -4,10 +4,12 @@ use smallvec::SmallVec; use crate::prelude::*; +/// A trait for buttons that can be Selected. Enables setting the [ButtonStyle] of a button when it is selected. pub trait SelectableButton: Selectable { fn selected_style(self, style: ButtonStyle) -> Self; } +/// A common set of traits all buttons must implement. pub trait ButtonCommon: Clickable + Disableable { /// A unique element ID to identify the button. fn id(&self) -> &ElementId; diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index 436461fddeaffa9b6d87af0de11b11f48cafec68..d7bd30187d6d816b01e0192a10845fa53401fe62 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/crates/ui/src/components/label/label_like.rs @@ -19,6 +19,7 @@ pub enum LineHeightStyle { UiLabel, } +/// A common set of traits all labels must implement. pub trait LabelCommon { fn size(self, size: LabelSize) -> Self; fn line_height_style(self, line_height_style: LineHeightStyle) -> Self; diff --git a/crates/ui/src/disableable.rs b/crates/ui/src/disableable.rs index f9b4e5ba910be1e65b6f2b106e9a45b707f20744..ebd34fba1cf8289a830b56516e027c7eabaf8f12 100644 --- a/crates/ui/src/disableable.rs +++ b/crates/ui/src/disableable.rs @@ -1,4 +1,4 @@ -/// A trait for elements that can be disabled. +/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing it's appearance to reflect that it is disabled. pub trait Disableable { /// Sets whether the element is disabled. fn disabled(self, disabled: bool) -> Self; diff --git a/crates/ui/src/fixed.rs b/crates/ui/src/fixed.rs index a2c3ed3edc819ed9a4bc4d4a33ff3b9494c11075..9ba64da0905310d2e3409e0c13d110cb81cc5e41 100644 --- a/crates/ui/src/fixed.rs +++ b/crates/ui/src/fixed.rs @@ -1,6 +1,6 @@ use gpui::DefiniteLength; -/// A trait for elements that have a fixed with. +/// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods. pub trait FixedWidth { /// Sets the width of the element. fn width(self, width: DefiniteLength) -> Self; diff --git a/crates/ui/src/selectable.rs b/crates/ui/src/selectable.rs index 34c66ab1fa4a4a961ee972588b9e3c7cf211308b..e5e60008b9194d129fb03523ead8e3d58c804681 100644 --- a/crates/ui/src/selectable.rs +++ b/crates/ui/src/selectable.rs @@ -1,18 +1,25 @@ -/// A trait for elements that can be selected. +/// A trait for elements that can be selected. Generally used to enable "toggle" or "active" behavior and styles on an element through the [Selection] status. pub trait Selectable { /// Sets whether the element is selected. fn selected(self, selected: bool) -> Self; } +/// Represents the selection status of an element. #[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)] pub enum Selection { + /// The element is not selected. #[default] Unselected, + /// The selection state of the element is indeterminate. Indeterminate, + /// The element is selected. Selected, } impl Selection { + /// Returns the inverse of the current selection status. + /// + /// Indeterminate states become selected if inverted. pub fn inverse(&self) -> Self { match self { Self::Unselected | Self::Indeterminate => Self::Selected, diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index a6381e604f5f672553a2b502a6e4814eee5f384d..8c5da763f360621a5a4a1d77ada46c56a6a05992 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -14,7 +14,7 @@ fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) - .shadow(index.shadow()) } -/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods. +/// Extends [gpui::Styled] with Zed specific styling methods. pub trait StyledExt: Styled + Sized { /// Horizontally stacks elements. /// @@ -119,26 +119,32 @@ pub trait StyledExt: Styled + Sized { self.border_color(cx.theme().colors().border_variant) } + /// Sets the background color to red for debugging when building UI. fn debug_bg_red(self) -> Self { self.bg(hsla(0. / 360., 1., 0.5, 1.)) } + /// Sets the background color to green for debugging when building UI. fn debug_bg_green(self) -> Self { self.bg(hsla(120. / 360., 1., 0.5, 1.)) } + /// Sets the background color to blue for debugging when building UI. fn debug_bg_blue(self) -> Self { self.bg(hsla(240. / 360., 1., 0.5, 1.)) } + /// Sets the background color to yellow for debugging when building UI. fn debug_bg_yellow(self) -> Self { self.bg(hsla(60. / 360., 1., 0.5, 1.)) } + /// Sets the background color to cyan for debugging when building UI. fn debug_bg_cyan(self) -> Self { self.bg(hsla(160. / 360., 1., 0.5, 1.)) } + /// Sets the background color to magenta for debugging when building UI. fn debug_bg_magenta(self) -> Self { self.bg(hsla(300. / 360., 1., 0.5, 1.)) } From cb24062e79fe18b8a1f18f6e15e16956c2360d04 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:16:50 -0500 Subject: [PATCH 043/334] Restore removed ColorScaleStep ALL that was breaking the build --- crates/theme/src/scale.rs | 40 +++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index 8bfb2999cda97f2da6d4dac816a85f51f3b564e6..1f562f16f06dd4f86be7267ef6ceca030c4dc65c 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -9,18 +9,34 @@ use crate::{ActiveTheme, Appearance}; pub struct ColorScaleStep(usize); impl ColorScaleStep { - const ONE: Self = Self(1); - const TWO: Self = Self(2); - const THREE: Self = Self(3); - const FOUR: Self = Self(4); - const FIVE: Self = Self(5); - const SIX: Self = Self(6); - const SEVEN: Self = Self(7); - const EIGHT: Self = Self(8); - const NINE: Self = Self(9); - const TEN: Self = Self(10); - const ELEVEN: Self = Self(11); - const TWELVE: Self = Self(12); + pub const ONE: Self = Self(1); + pub const TWO: Self = Self(2); + pub const THREE: Self = Self(3); + pub const FOUR: Self = Self(4); + pub const FIVE: Self = Self(5); + pub const SIX: Self = Self(6); + pub const SEVEN: Self = Self(7); + pub const EIGHT: Self = Self(8); + pub const NINE: Self = Self(9); + pub const TEN: Self = Self(10); + pub const ELEVEN: Self = Self(11); + pub const TWELVE: Self = Self(12); + + /// All of the steps in a [`ColorScale`]. + pub const ALL: [ColorScaleStep; 12] = [ + Self::ONE, + Self::TWO, + Self::THREE, + Self::FOUR, + Self::FIVE, + Self::SIX, + Self::SEVEN, + Self::EIGHT, + Self::NINE, + Self::TEN, + Self::ELEVEN, + Self::TWELVE, + ]; } /// A scale of colors for a given [ColorScaleSet]. From c19551d9746522019e5364ea5a5894fbfe4cf388 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:21:29 -0500 Subject: [PATCH 044/334] Document free functions --- crates/ui/src/components/popover_menu.rs | 1 + crates/ui/src/components/right_click_menu.rs | 1 + crates/ui/src/components/stack.rs | 8 ++------ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index fb823b05dba3d18cea7992948abb50f827167e1c..6bfc88bbec3ef292d1163717b0f1aaad7ec2c2fb 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -108,6 +108,7 @@ impl PopoverMenu { } } +/// Creates a [PopoverMenu] pub fn popover_menu(id: impl Into) -> PopoverMenu { PopoverMenu { id: id.into(), diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 8bf40f61a8264ec3b00eec3ec844efa247876da7..282884c948b757b9ba305e8ee5532e7d5732796e 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -39,6 +39,7 @@ impl RightClickMenu { } } +/// Creates a [RightClickMenu] pub fn right_click_menu(id: impl Into) -> RightClickMenu { RightClickMenu { id: id.into(), diff --git a/crates/ui/src/components/stack.rs b/crates/ui/src/components/stack.rs index a6321b93d7c9e15afa1d3053fa61fb467acdbeb0..76f08de911e8e1e60a67c1d9311355f62d55914b 100644 --- a/crates/ui/src/components/stack.rs +++ b/crates/ui/src/components/stack.rs @@ -2,17 +2,13 @@ use gpui::{div, Div}; use crate::StyledExt; -/// Horizontally stacks elements. -/// -/// Sets `flex()`, `flex_row()`, `items_center()` +/// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()` #[track_caller] pub fn h_stack() -> Div { div().h_flex() } -/// Vertically stacks elements. -/// -/// Sets `flex()`, `flex_col()` +/// Vertically stacks elements. Sets `flex()`, `flex_col()` #[track_caller] pub fn v_stack() -> Div { div().v_flex() From 463270ed36e4d24ce8ded0a30a06b889ee128939 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 9 Jan 2024 14:32:43 -0500 Subject: [PATCH 045/334] Take into account multiple scroll deltas within a single frame Co-Authored-By: Antonio Scandurra --- crates/editor/src/element.rs | 101 ++++++++++++++++++--------------- crates/gpui/src/interactive.rs | 14 +++++ 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c7fbb658a3cf106e1ee7cc6212cf67a18481c84c..fc3c53f34343684ed5d39305d1c30d4fe7872881 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -28,7 +28,7 @@ use gpui::{ AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollWheelEvent, ShapedLine, + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, }; @@ -581,41 +581,6 @@ impl EditorElement { } } - fn scroll( - editor: &mut Editor, - event: &ScrollWheelEvent, - position_map: &PositionMap, - bounds: &InteractiveBounds, - cx: &mut ViewContext, - ) { - if !bounds.visibly_contains(&event.position, cx) { - return; - } - - let line_height = position_map.line_height; - let max_glyph_width = position_map.em_width; - let (delta, axis) = match event.delta { - gpui::ScrollDelta::Pixels(mut pixels) => { - //Trackpad - let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels); - (pixels, axis) - } - - gpui::ScrollDelta::Lines(lines) => { - //Not trackpad - let pixels = point(lines.x * max_glyph_width, lines.y * line_height); - (pixels, None) - } - }; - - let scroll_position = position_map.snapshot.scroll_position(); - let x = f32::from((scroll_position.x * max_glyph_width - delta.x) / max_glyph_width); - let y = f32::from((scroll_position.y * line_height - delta.y) / line_height); - let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); - editor.scroll(scroll_position, axis, cx); - cx.stop_propagation(); - } - fn paint_background( &self, gutter_bounds: Bounds, @@ -2450,34 +2415,78 @@ impl EditorElement { ) } - fn paint_mouse_listeners( + fn paint_scroll_wheel_listener( &mut self, - bounds: Bounds, - gutter_bounds: Bounds, - text_bounds: Bounds, + interactive_bounds: &InteractiveBounds, layout: &LayoutState, cx: &mut WindowContext, ) { - let interactive_bounds = InteractiveBounds { - bounds: bounds.intersect(&cx.content_mask().bounds), - stacking_order: cx.stacking_order().clone(), - }; - cx.on_mouse_event({ let position_map = layout.position_map.clone(); let editor = self.editor.clone(); let interactive_bounds = interactive_bounds.clone(); + let mut delta = ScrollDelta::default(); move |event: &ScrollWheelEvent, phase, cx| { if phase == DispatchPhase::Bubble && interactive_bounds.visibly_contains(&event.position, cx) { + delta = delta.coalesce(event.delta); editor.update(cx, |editor, cx| { - Self::scroll(editor, event, &position_map, &interactive_bounds, cx) + let position = event.position; + let position_map: &PositionMap = &position_map; + let bounds = &interactive_bounds; + if !bounds.visibly_contains(&position, cx) { + return; + } + + let line_height = position_map.line_height; + let max_glyph_width = position_map.em_width; + let (delta, axis) = match delta { + gpui::ScrollDelta::Pixels(mut pixels) => { + //Trackpad + let axis = position_map.snapshot.ongoing_scroll.filter(&mut pixels); + (pixels, axis) + } + + gpui::ScrollDelta::Lines(lines) => { + //Not trackpad + let pixels = + point(lines.x * max_glyph_width, lines.y * line_height); + (pixels, None) + } + }; + + let scroll_position = position_map.snapshot.scroll_position(); + let x = f32::from( + (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width, + ); + let y = + f32::from((scroll_position.y * line_height - delta.y) / line_height); + let scroll_position = + point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); + editor.scroll(scroll_position, axis, cx); + cx.stop_propagation(); }); } } }); + } + + fn paint_mouse_listeners( + &mut self, + bounds: Bounds, + gutter_bounds: Bounds, + text_bounds: Bounds, + layout: &LayoutState, + cx: &mut WindowContext, + ) { + let interactive_bounds = InteractiveBounds { + bounds: bounds.intersect(&cx.content_mask().bounds), + stacking_order: cx.stacking_order().clone(), + }; + + self.paint_scroll_wheel_listener(&interactive_bounds, layout, cx); cx.on_mouse_event({ let position_map = layout.position_map.clone(); diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 6f396d31aa481571d3331e816f30dbf788d47816..dfccfc35307f1eb2e75e2d7e8fe8eb73b2c4b7ef 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -178,6 +178,20 @@ impl ScrollDelta { ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y), } } + + pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta { + match (self, other) { + (ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => { + ScrollDelta::Pixels(px_a + px_b) + } + + (ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => { + ScrollDelta::Lines(lines_a + lines_b) + } + + _ => other, + } + } } #[derive(Clone, Debug, Default)] From 74dadd68d251e2b9cd93158bad2d1c4c726ac311 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 14:48:48 -0500 Subject: [PATCH 046/334] Clean up references in doc comments (#3983) This PR cleans up a number of references in doc comments so that `rustdoc` will link and display them correctly. Release Notes: - N/A --- crates/gpui/src/app.rs | 10 ++--- crates/gpui/src/element.rs | 10 ++--- crates/gpui/src/executor.rs | 8 ++-- crates/gpui/src/input.rs | 2 +- crates/gpui/src/platform/mac/display.rs | 4 +- crates/gpui/src/subscription.rs | 6 +-- crates/gpui/src/window.rs | 50 ++++++++++++------------- crates/rope/src/rope.rs | 6 +-- crates/text/src/anchor.rs | 2 +- 9 files changed, 50 insertions(+), 48 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index f84ae809d34b14712495de95043e68ae28fe88a3..108ad28d24a16191b6a419d09b9d56bf212c4050 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -163,17 +163,17 @@ impl App { self.0.borrow().app_metadata.clone() } - /// Returns a handle to the [BackgroundExecutor] associated with this app, which can be used to spawn futures in the background. + /// Returns a handle to the [`BackgroundExecutor`] associated with this app, which can be used to spawn futures in the background. pub fn background_executor(&self) -> BackgroundExecutor { self.0.borrow().background_executor.clone() } - /// Returns a handle to the [ForegroundExecutor] associated with this app, which can be used to spawn futures in the foreground. + /// Returns a handle to the [`ForegroundExecutor`] associated with this app, which can be used to spawn futures in the foreground. pub fn foreground_executor(&self) -> ForegroundExecutor { self.0.borrow().foreground_executor.clone() } - /// Returns a reference to the [TextSystem] associated with this app. + /// Returns a reference to the [`TextSystem`] associated with this app. pub fn text_system(&self) -> Arc { self.0.borrow().text_system.clone() } @@ -299,7 +299,7 @@ impl AppContext { app } - /// Quit the application gracefully. Handlers registered with `ModelContext::on_app_quit` + /// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`] /// will be given 100ms to complete before exiting. pub fn shutdown(&mut self) { let mut futures = Vec::new(); @@ -580,7 +580,7 @@ impl AppContext { self.pending_effects.push_back(effect); } - /// Called at the end of AppContext::update to complete any side effects + /// Called at the end of [`AppContext::update`] to complete any side effects /// such as notifying observers, emitting events, etc. Effects can themselves /// cause effects, so we continue looping until all effects are processed. fn flush_effects(&mut self) { diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 987b91b791a93b9fb72672a4608c1b6665fc20e2..179c2cb1e25db449556e92cfbf9710278dbd2b73 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -31,14 +31,14 @@ pub trait IntoElement: Sized { /// The specific type of element into which the implementing type is converted. type Element: Element; - /// The [ElementId] of self once converted into an [Element]. + /// The [`ElementId`] of self once converted into an [`Element`]. /// If present, the resulting element's state will be carried across frames. fn element_id(&self) -> Option; - /// Convert self into a type that implements [Element]. + /// Convert self into a type that implements [`Element`]. fn into_element(self) -> Self::Element; - /// Convert self into a dynamically-typed [AnyElement]. + /// Convert self into a dynamically-typed [`AnyElement`]. fn into_any_element(self) -> AnyElement { self.into_element().into_any() } @@ -115,7 +115,7 @@ pub trait Render: 'static + Sized { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement; } -/// You can derive [IntoElement] on any type that implements this trait. +/// You can derive [`IntoElement`] on any type that implements this trait. /// It is used to allow views to be expressed in terms of abstract data. pub trait RenderOnce: 'static { fn render(self, cx: &mut WindowContext) -> impl IntoElement; @@ -224,7 +224,7 @@ enum ElementDrawPhase { }, } -/// A wrapper around an implementer of [Element] that allows it to be drawn in a window. +/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window. impl DrawableElement { fn new(element: E) -> Self { DrawableElement { diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index 4be1ffbf0fdf970353d6f2402eda02ab2b0faac8..fc60cb1ec6afcd1c79f5b561a436eac4635c47bc 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -33,8 +33,10 @@ pub struct ForegroundExecutor { } /// Task is a primitive that allows work to happen in the background. -/// It implements Future so you can `.await` on it. -/// If you drop a task it will be cancelled immediately. Calling `.detach()` allows +/// +/// It implements [`Future`] so you can `.await` on it. +/// +/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows /// the task to continue running in the background, but with no way to return a value. #[must_use] #[derive(Debug)] @@ -387,7 +389,7 @@ impl ForegroundExecutor { } } -/// Scope manages a set of tasks that are enqueued and waited on together. See `BackgroundExecutor#scoped` +/// Scope manages a set of tasks that are enqueued and waited on together. See [`BackgroundExecutor::scoped`]. pub struct Scope<'a> { executor: BackgroundExecutor, futures: Vec + Send + 'static>>>, diff --git a/crates/gpui/src/input.rs b/crates/gpui/src/input.rs index da240a77a8b633683d11d96356451dd889e17818..7290b48abd7157cdbf11ffa74e6eed294bcc271e 100644 --- a/crates/gpui/src/input.rs +++ b/crates/gpui/src/input.rs @@ -34,7 +34,7 @@ pub trait InputHandler: 'static + Sized { ) -> Option>; } -/// The canonical implementation of `PlatformInputHandler`. Call `WindowContext::handle_input` +/// The canonical implementation of [`PlatformInputHandler`]. Call [`WindowContext::handle_input`] /// with an instance during your element's paint. pub struct ElementInputHandler { view: View, diff --git a/crates/gpui/src/platform/mac/display.rs b/crates/gpui/src/platform/mac/display.rs index 123cbf8159be02b0496bf8e3656f837e63b03bd4..25e0921fee28efd25c0c1ddf88d6df84318a19aa 100644 --- a/crates/gpui/src/platform/mac/display.rs +++ b/crates/gpui/src/platform/mac/display.rs @@ -14,12 +14,12 @@ pub struct MacDisplay(pub(crate) CGDirectDisplayID); unsafe impl Send for MacDisplay {} impl MacDisplay { - /// Get the screen with the given [DisplayId]. + /// Get the screen with the given [`DisplayId`]. pub fn find_by_id(id: DisplayId) -> Option { Self::all().find(|screen| screen.id() == id) } - /// Get the screen with the given persistent [Uuid]. + /// Get the screen with the given persistent [`Uuid`]. pub fn find_by_uuid(uuid: Uuid) -> Option { Self::all().find(|screen| screen.uuid().ok() == Some(uuid)) } diff --git a/crates/gpui/src/subscription.rs b/crates/gpui/src/subscription.rs index b56c9a1ccdbe800d5e24e8a4a353ef4ff08c784c..887283d09486ae139e219f5910206eda317b741b 100644 --- a/crates/gpui/src/subscription.rs +++ b/crates/gpui/src/subscription.rs @@ -37,10 +37,10 @@ where }))) } - /// Inserts a new `[Subscription]` for the given `emitter_key`. By default, subscriptions + /// Inserts a new [`Subscription`] for the given `emitter_key`. By default, subscriptions /// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`. - /// This method returns a tuple of a `[Subscription]` and an `impl FnOnce`, and you can use the latter - /// to activate the `[Subscription]`. + /// This method returns a tuple of a [`Subscription`] and an `impl FnOnce`, and you can use the latter + /// to activate the [`Subscription`]. #[must_use] pub fn insert( &self, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 9b657a3f71c8fe226008d4ed56b787f17bc65a8c..ec4713639e930cd759a8af5cb4435ea588c5ec30 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -248,7 +248,7 @@ pub trait ManagedView: FocusableView + EventEmitter {} impl> ManagedView for M {} -/// Emitted by implementers of [ManagedView] to indicate the view should be dismissed, such as when a view is presented as a modal. +/// Emitted by implementers of [`ManagedView`] to indicate the view should be dismissed, such as when a view is presented as a modal. pub struct DismissEvent; // Holds the state for a specific window. @@ -464,8 +464,8 @@ impl ContentMask { } /// Provides access to application state in the context of a single window. Derefs -/// to an `AppContext`, so you can also pass a `WindowContext` to any method that takes -/// an `AppContext` and call any `AppContext` methods. +/// to an [`AppContext`], so you can also pass a [`WindowContext`] to any method that takes +/// an [`AppContext`] and call any [`AppContext`] methods. pub struct WindowContext<'a> { pub(crate) app: &'a mut AppContext, pub(crate) window: &'a mut Window, @@ -493,20 +493,20 @@ impl<'a> WindowContext<'a> { self.window.removed = true; } - /// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus + /// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus /// for elements rendered within this window. pub fn focus_handle(&mut self) -> FocusHandle { FocusHandle::new(&self.window.focus_handles) } - /// Obtain the currently focused `FocusHandle`. If no elements are focused, returns `None`. + /// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`. pub fn focused(&self) -> Option { self.window .focus .and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles)) } - /// Move focus to the element associated with the given `FocusHandle`. + /// Move focus to the element associated with the given [`FocusHandle`]. pub fn focus(&mut self, handle: &FocusHandle) { if !self.window.focus_enabled || self.window.focus == Some(handle.id) { return; @@ -605,8 +605,8 @@ impl<'a> WindowContext<'a> { } /// Subscribe to events emitted by a model or view. - /// The entity to which you're subscribing must implement the [EventEmitter] trait. - /// The callback will be invoked a handle to the emitting entity (either a [View] or [Model]), the event, and a window context for the current window. + /// The entity to which you're subscribing must implement the [`EventEmitter`] trait. + /// The callback will be invoked a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a window context for the current window. pub fn subscribe( &mut self, entity: &E, @@ -2061,7 +2061,7 @@ impl<'a> BorrowMut for WindowContext<'a> { } } -/// This trait contains functionality that is shared across [ViewContext] and [WindowContext] +/// This trait contains functionality that is shared across [`ViewContext`] and [`WindowContext`] pub trait BorrowWindow: BorrowMut + BorrowMut { #[doc(hidden)] fn app_mut(&mut self) -> &mut AppContext { @@ -2328,10 +2328,10 @@ impl BorrowMut for WindowContext<'_> { impl BorrowWindow for T where T: BorrowMut + BorrowMut {} -/// Provides access to application state that is specialized for a particular [View]. +/// Provides access to application state that is specialized for a particular [`View`]. /// Allows you to interact with focus, emit events, etc. -/// ViewContext also derefs to [WindowContext], giving you access to all of its methods as well. -/// When you call [View::::update], you're passed a `&mut V` and an `&mut ViewContext`. +/// ViewContext also derefs to [`WindowContext`], giving you access to all of its methods as well. +/// When you call [`View::update`], you're passed a `&mut V` and an `&mut ViewContext`. pub struct ViewContext<'a, V> { window_cx: WindowContext<'a>, view: &'a View, @@ -2407,7 +2407,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } - /// Observe another model or view for changes to it's state, as tracked by the [AppContext::notify] + /// Observe another model or view for changes to its state, as tracked by [`ModelContext::notify`]. pub fn observe( &mut self, entity: &E, @@ -2442,8 +2442,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { } /// Subscribe to events emitted by another model or view. - /// The entity to which you're subscribing must implement the [EventEmitter] trait. - /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [View] or [Model]), the event, and a view context for the current view. + /// The entity to which you're subscribing must implement the [`EventEmitter`] trait. + /// The callback will be invoked with a reference to the current view, a handle to the emitting entity (either a [`View`] or [`Model`]), the event, and a view context for the current view. pub fn subscribe( &mut self, entity: &E, @@ -2687,8 +2687,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { } /// Schedule a future to be run asynchronously. - /// The given callback is invoked with a [WeakView] to avoid leaking the view for a long-running process. - /// It's also given an `AsyncWindowContext`, which can be used to access the state of the view across await points. + /// The given callback is invoked with a [`WeakView`] to avoid leaking the view for a long-running process. + /// It's also given an [`AsyncWindowContext`], which can be used to access the state of the view across await points. /// The returned future will be polled on the main thread. pub fn spawn( &mut self, @@ -2789,7 +2789,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { }); } - /// Move focus to the current view, assuming it implements [FocusableView]. + /// Move focus to the current view, assuming it implements [`FocusableView`]. pub fn focus_self(&mut self) where V: FocusableView, @@ -3119,21 +3119,21 @@ impl AnyWindowHandle { // } // } -/// An identifier for an [Element]. +/// An identifier for an [`Element`](crate::Element). /// /// Can be constructed with a string, a number, or both, as well /// as other internal representations. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { - /// The id of a View element + /// The ID of a View element View(EntityId), - /// An integer id + /// An integer ID. Integer(usize), - /// A string based id + /// A string based ID. Name(SharedString), - /// An id that's equated with a focus handle + /// An ID that's equated with a focus handle. FocusHandle(FocusId), - /// A combination of a name and an integer + /// A combination of a name and an integer. NamedInteger(SharedString, usize), } @@ -3204,7 +3204,7 @@ impl From<(&'static str, u64)> for ElementId { } /// A rectangle to be rendered in the window at the given position and size. -/// Passed as an argument [WindowContext::paint_quad]. +/// Passed as an argument [`WindowContext::paint_quad`]. #[derive(Clone)] pub struct PaintQuad { bounds: Bounds, diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index adfcd19a6c5420f12fab5495731cea7b9e70397a..05873818c7a5601f494ccc0cd87d3f5d25cf94cd 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -25,10 +25,10 @@ const CHUNK_BASE: usize = 6; #[cfg(not(test))] const CHUNK_BASE: usize = 16; -/// Type alias to [HashMatrix], an implementation of a homomorphic hash function. Two [Rope] instances +/// Type alias to [`HashMatrix`], an implementation of a homomorphic hash function. Two [`Rope`] instances /// containing the same text will produce the same fingerprint. This hash function is special in that -/// it allows us to hash individual chunks and aggregate them up the [Rope]'s tree, with the resulting -/// hash being equivalent to hashing all the text contained in the [Rope] at once. +/// it allows us to hash individual chunks and aggregate them up the [`Rope`]'s tree, with the resulting +/// hash being equivalent to hashing all the text contained in the [`Rope`] at once. pub type RopeFingerprint = HashMatrix; #[derive(Clone, Default)] diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index 084be0e336e5ddbdaa2e5bd683aac763e5ca1735..a65e3753d49ae99285931646ffaa3dd7906818cf 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -90,7 +90,7 @@ impl Anchor { content.summary_for_anchor(self) } - /// Returns true when the [Anchor] is located inside a visible fragment. + /// Returns true when the [`Anchor`] is located inside a visible fragment. pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { if *self == Anchor::MIN || *self == Anchor::MAX { true From 51988f63d5eabc0f4461b5aa2a71495452d54ab1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 9 Jan 2024 14:50:04 -0500 Subject: [PATCH 047/334] Document more enums --- crates/ui/src/components/button/button_like.rs | 4 ++-- crates/ui/src/components/divider.rs | 1 + crates/ui/src/styled_ext.rs | 7 ++++--- crates/ui/src/styles/color.rs | 1 + crates/ui/src/styles/elevation.rs | 1 + crates/ui/src/styles/typography.rs | 1 + 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index d6ecad41fc657860ba31622810e8459cb4a72072..2c292595c751c136b4c83b4832f5b49dda81c17a 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -95,6 +95,7 @@ impl From for Color { } } +/// Sets the visual appearance of a button. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { /// A filled button with a solid background color. Provides emphasis versus @@ -262,8 +263,7 @@ impl ButtonStyle { } } -/// ButtonSize can also be used to help build non-button elements -/// that are consistently sized with buttons. +/// Sets the height of a button. Can also be used to size non-button elements to align with [Button]s. #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonSize { Large, diff --git a/crates/ui/src/components/divider.rs b/crates/ui/src/components/divider.rs index 2567c3fc3476c72819f0d30f5eae2a3e74f68059..dfe31b677ba6fb8b1d4a2878b97a9ad947856cac 100644 --- a/crates/ui/src/components/divider.rs +++ b/crates/ui/src/components/divider.rs @@ -7,6 +7,7 @@ enum DividerDirection { Vertical, } +/// Sets the color of a [Divider]. #[derive(Default)] pub enum DividerColor { Border, diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 8c5da763f360621a5a4a1d77ada46c56a6a05992..1f47fe4f140cfc0e0766ecf5a028c382a8d22b73 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -30,6 +30,7 @@ pub trait StyledExt: Styled + Sized { self.flex().flex_col() } + /// Sets the text size using a [UiTextSize]. fn text_ui_size(self, size: UiTextSize) -> Self { self.text_size(size.rems()) } @@ -40,7 +41,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui_sm`] for regular-sized text. + /// Use `text_ui_sm` for smaller text. fn text_ui(self) -> Self { self.text_size(UiTextSize::default().rems()) } @@ -51,7 +52,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui`] for regular-sized text. + /// Use `text_ui` for regular-sized text. fn text_ui_sm(self) -> Self { self.text_size(UiTextSize::Small.rems()) } @@ -62,7 +63,7 @@ pub trait StyledExt: Styled + Sized { /// /// Note: The absolute size of this text will change based on a user's `ui_scale` setting. /// - /// Use [`text_ui`] for regular-sized text. + /// Use `text_ui` for regular-sized text. fn text_ui_xs(self) -> Self { self.text_size(UiTextSize::XSmall.rems()) } diff --git a/crates/ui/src/styles/color.rs b/crates/ui/src/styles/color.rs index 977a26dedce61d20bb27ee6b65bcc9f3f8738096..434183e5606135cdcb7e420c023c1108d0aa0a42 100644 --- a/crates/ui/src/styles/color.rs +++ b/crates/ui/src/styles/color.rs @@ -1,6 +1,7 @@ use gpui::{Hsla, WindowContext}; use theme::ActiveTheme; +/// Sets a color that has a consistent meaning across all themes. #[derive(Debug, Default, PartialEq, Copy, Clone)] pub enum Color { #[default] diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index 7b3835c2e54b3e60528ab212311d245dfd7223d3..055a419e072afa959e12fe2d2f5b4942b34679a1 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -85,6 +85,7 @@ impl LayerIndex { } } +/// Sets ann appropriate z-index for the given layer based on it's intended useage. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementIndex { Effect, diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index 39937ebff1701f2195c4112235c919368b97b364..20f5e5f48f3815559d3ba48cdbe27ea6d242ebfb 100644 --- a/crates/ui/src/styles/typography.rs +++ b/crates/ui/src/styles/typography.rs @@ -38,6 +38,7 @@ impl UiTextSize { } } +/// Sets the size of a [Headline] element #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum HeadlineSize { XSmall, From 1728c4eacca01fe6750869d89bb65c24f999a6e0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 11:52:03 -0800 Subject: [PATCH 048/334] Fixed test --- crates/gpui/src/key_dispatch.rs | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index c65b169596c8b8627521e029d61ae59e24b3845b..cc4af6d7e30d4bcc546ba264d1999cdbf5bec26c 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -192,9 +192,8 @@ impl DispatchTree { keymap .bindings_for_action(action) .filter(|binding| { - for i in 1..context_stack.len() { - dbg!(i); - let context = &context_stack[0..i]; + for i in 0..context_stack.len() { + let context = &context_stack[0..=i]; if keymap.binding_enabled(binding, context) { return true; } @@ -333,36 +332,26 @@ mod tests { #[test] fn test_keybinding_for_action_bounds() { - dbg!("got here"); - let keymap = Keymap::new(vec![KeyBinding::new( "cmd-n", TestAction, Some("ProjectPanel"), )]); - dbg!("got here"); let mut registry = ActionRegistry::default(); - dbg!("got here"); registry.load_action::(); - dbg!("got here"); - let keymap = Arc::new(Mutex::new(keymap)); - dbg!("got here"); let tree = DispatchTree::new(keymap, Rc::new(registry)); - dbg!("got here"); - let keybinding = tree.bindings_for_action( - &TestAction, - &vec![ - KeyContext::parse(",").unwrap(), - KeyContext::parse("Workspace").unwrap(), - KeyContext::parse("ProjectPanel").unwrap(), - ], - ); + let contexts = vec![ + KeyContext::parse("Workspace").unwrap(), + KeyContext::parse("ProjectPanel").unwrap(), + ]; + + let keybinding = tree.bindings_for_action(&TestAction, &contexts); assert!(keybinding[0].action.partial_eq(&TestAction)) } From 128a8ff0b91dfd4db2657ffa0a780d87d33eb216 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:02:29 -0800 Subject: [PATCH 049/334] Remove unnecessary mutexes from livekit client types Co-authored-by: Conrad --- crates/live_kit_client/src/prod.rs | 60 ++++++++++++++---------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index b2b83e95fcf33543aed6d548b21a5f07d6f6b400..74cf8423bc57f638d5dc3602f765e4f656fe9df0 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -164,29 +164,26 @@ pub enum ConnectionState { } pub struct Room { - native_room: Mutex, + native_room: swift::Room, connection: Mutex<( watch::Sender, watch::Receiver, )>, remote_audio_track_subscribers: Mutex>>, remote_video_track_subscribers: Mutex>>, - _delegate: Mutex, + _delegate: RoomDelegate, } -trait AssertSendSync: Send {} -impl AssertSendSync for Room {} - impl Room { pub fn new() -> Arc { Arc::new_cyclic(|weak_room| { let delegate = RoomDelegate::new(weak_room.clone()); Self { - native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }), + native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), remote_audio_track_subscribers: Default::default(), remote_video_track_subscribers: Default::default(), - _delegate: Mutex::new(delegate), + _delegate: delegate, } }) } @@ -201,7 +198,7 @@ impl Room { let (did_connect, tx, rx) = Self::build_done_callback(); unsafe { LKRoomConnect( - *self.native_room.lock(), + self.native_room, url.as_concrete_TypeRef(), token.as_concrete_TypeRef(), did_connect, @@ -271,7 +268,7 @@ impl Room { } unsafe { LKRoomPublishVideoTrack( - *self.native_room.lock(), + self.native_room, track.0, callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -301,7 +298,7 @@ impl Room { } unsafe { LKRoomPublishAudioTrack( - *self.native_room.lock(), + self.native_room, track.0, callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -312,14 +309,14 @@ impl Room { pub fn unpublish_track(&self, publication: LocalTrackPublication) { unsafe { - LKRoomUnpublishTrack(*self.native_room.lock(), publication.0); + LKRoomUnpublishTrack(self.native_room, publication.0); } } pub fn remote_video_tracks(&self, participant_id: &str) -> Vec> { unsafe { let tracks = LKRoomVideoTracksForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -348,7 +345,7 @@ impl Room { pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec> { unsafe { let tracks = LKRoomAudioTracksForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -380,7 +377,7 @@ impl Room { ) -> Vec> { unsafe { let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant( - *self.native_room.lock(), + self.native_room, CFString::new(participant_id).as_concrete_TypeRef(), ); @@ -508,9 +505,8 @@ impl Room { impl Drop for Room { fn drop(&mut self) { unsafe { - let native_room = &*self.native_room.lock(); - LKRoomDisconnect(*native_room); - CFRelease(native_room.0); + LKRoomDisconnect(self.native_room); + CFRelease(self.native_room.0); } } } @@ -726,7 +722,7 @@ impl Drop for LocalTrackPublication { } pub struct RemoteTrackPublication { - native_publication: Mutex, + native_publication: swift::RemoteTrackPublication, } impl RemoteTrackPublication { @@ -735,21 +731,19 @@ impl RemoteTrackPublication { CFRetain(native_track_publication.0); } Self { - native_publication: Mutex::new(native_track_publication), + native_publication: native_track_publication, } } pub fn sid(&self) -> String { unsafe { - CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid( - *self.native_publication.lock(), - )) - .to_string() + CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.native_publication)) + .to_string() } } pub fn is_muted(&self) -> bool { - unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) } + unsafe { LKRemoteTrackPublicationIsMuted(self.native_publication) } } pub fn set_enabled(&self, enabled: bool) -> impl Future> { @@ -767,7 +761,7 @@ impl RemoteTrackPublication { unsafe { LKRemoteTrackPublicationSetEnabled( - *self.native_publication.lock(), + self.native_publication, enabled, complete_callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -780,13 +774,13 @@ impl RemoteTrackPublication { impl Drop for RemoteTrackPublication { fn drop(&mut self) { - unsafe { CFRelease((*self.native_publication.lock()).0) } + unsafe { CFRelease(self.native_publication.0) } } } #[derive(Debug)] pub struct RemoteAudioTrack { - native_track: Mutex, + native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String, } @@ -797,7 +791,7 @@ impl RemoteAudioTrack { CFRetain(native_track.0); } Self { - native_track: Mutex::new(native_track), + native_track, sid, publisher_id, } @@ -822,13 +816,13 @@ impl RemoteAudioTrack { impl Drop for RemoteAudioTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.lock().0) } + unsafe { CFRelease(self.native_track.0) } } } #[derive(Debug)] pub struct RemoteVideoTrack { - native_track: Mutex, + native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String, } @@ -839,7 +833,7 @@ impl RemoteVideoTrack { CFRetain(native_track.0); } Self { - native_track: Mutex::new(native_track), + native_track, sid, publisher_id, } @@ -888,7 +882,7 @@ impl RemoteVideoTrack { on_frame, on_drop, ); - LKVideoTrackAddRenderer(*self.native_track.lock(), renderer); + LKVideoTrackAddRenderer(self.native_track, renderer); rx } } @@ -896,7 +890,7 @@ impl RemoteVideoTrack { impl Drop for RemoteVideoTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.lock().0) } + unsafe { CFRelease(self.native_track.0) } } } From 145f3f55e9bc61d470c767b487a675df69a56c63 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 12:20:17 -0800 Subject: [PATCH 050/334] Remove unused API --- crates/activity_indicator/src/activity_indicator.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 4b990fa430cf5c652457ee42a1dc57915a7a1b8c..d04a5ab319f4c116a319a0a6f4d7b51a1de51184 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -77,9 +77,6 @@ impl ActivityIndicator { cx.observe(auto_updater, |_, _, cx| cx.notify()).detach(); } - // cx.observe_active_labeled_tasks(|_, cx| cx.notify()) - // .detach(); - Self { statuses: Default::default(), project: project.clone(), @@ -288,15 +285,6 @@ impl ActivityIndicator { }; } - // todo!(show active tasks) - // if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() { - // return Content { - // icon: None, - // message: most_recent_active_task.to_string(), - // on_click: None, - // }; - // } - Default::default() } } From 7ed3f5f392fcdf1e20669d24817b49d9006225d5 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 15:22:36 -0500 Subject: [PATCH 051/334] Clean up references in doc comments in `ui` and `theme` crates (#3985) This PR cleans up a number of references in doc comments in the `ui` and `theme` crates so that `rustdoc` will link and display them correctly. Release Notes: - N/A --- crates/theme/src/scale.rs | 8 +++---- crates/ui/src/components/avatar.rs | 2 +- crates/ui/src/components/button/button.rs | 24 +++++++++---------- .../ui/src/components/button/button_like.rs | 8 ++++--- crates/ui/src/components/divider.rs | 2 +- crates/ui/src/components/label/label.rs | 6 ++--- crates/ui/src/components/popover_menu.rs | 2 +- crates/ui/src/components/right_click_menu.rs | 2 +- crates/ui/src/prelude.rs | 2 +- crates/ui/src/selectable.rs | 4 +++- crates/ui/src/styled_ext.rs | 10 ++++---- crates/ui/src/styles/elevation.rs | 2 +- crates/ui/src/styles/typography.rs | 2 +- crates/ui/src/utils/format_distance.rs | 20 ++++++++-------- 14 files changed, 49 insertions(+), 45 deletions(-) diff --git a/crates/theme/src/scale.rs b/crates/theme/src/scale.rs index 1f562f16f06dd4f86be7267ef6ceca030c4dc65c..1146090edcc1e7fbef1f8cc09e43377db4183862 100644 --- a/crates/theme/src/scale.rs +++ b/crates/theme/src/scale.rs @@ -39,10 +39,10 @@ impl ColorScaleStep { ]; } -/// A scale of colors for a given [ColorScaleSet]. +/// A scale of colors for a given [`ColorScaleSet`]. /// -/// Each [ColorScale] contains exactly 12 colors. Refer to -/// [ColorScaleStep] for a reference of what each step is used for. +/// Each [`ColorScale`] contains exactly 12 colors. Refer to +/// [`ColorScaleStep`] for a reference of what each step is used for. pub struct ColorScale(Vec); impl FromIterator for ColorScale { @@ -235,7 +235,7 @@ impl IntoIterator for ColorScales { } } -/// Provides groups of [ColorScale]s for light and dark themes, as well as transparent versions of each scale. +/// Provides groups of [`ColorScale`]s for light and dark themes, as well as transparent versions of each scale. pub struct ColorScaleSet { name: SharedString, light: ColorScale, diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 8e13bc9faa8f2e48227ab51757c847785043b51d..98a48de6863af8cfd171dd67cee2604b395261f2 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -78,7 +78,7 @@ impl Avatar { /// Sets the shape of the avatar image. /// - /// This method allows the shape of the avatar to be specified using the [Shape] enum. + /// This method allows the shape of the avatar to be specified using a [`Shape`]. /// It modifies the corner radius of the image to match the specified shape. /// /// # Examples diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index b65bf050eb87c3055c1c4eb923ff2c9f2b9c4ee9..b241b519cdab78c08168eeb1174ab24cc067fc5e 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -10,12 +10,12 @@ use super::button_icon::ButtonIcon; /// An element that creates a button with a label and an optional icon. /// /// Common buttons: -/// - Label, Icon + Label: [Button] (this component) -/// - Icon only: [IconButton] -/// - Custom: [ButtonLike] +/// - Label, Icon + Label: [`Button`] (this component) +/// - Icon only: [`IconButton`] +/// - Custom: [`ButtonLike`] /// -/// To create a more complex button than what the [Button] or [IconButton] components provide, use -/// [ButtonLike] directly. +/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use +/// [`ButtonLike`] directly. /// /// # Examples /// @@ -42,7 +42,7 @@ use super::button_icon::ButtonIcon; /// }); /// ``` /// -/// To change the style of the button when it is selected use the [selected_style][Button::selected_style] method. +/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method. /// /// ``` /// Button::new("button_id", "Click me!") @@ -81,9 +81,9 @@ pub struct Button { } impl Button { - /// Creates a new [Button] with a specified identifier and label. + /// Creates a new [`Button`] with a specified identifier and label. /// - /// This is the primary constructor for a `Button` component. It initializes + /// This is the primary constructor for a [`Button`] component. It initializes /// the button with the provided identifier and label text, setting all other /// properties to their default values, which can be customized using the /// builder pattern methods provided by this struct. @@ -174,7 +174,7 @@ impl Selectable for Button { /// }); /// ``` /// - /// Use [selected_style](Button::selected_style) to change the style of the button when it is selected. + /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected. fn selected(mut self, selected: bool) -> Self { self.base = self.base.selected(selected); self @@ -282,13 +282,13 @@ impl ButtonCommon for Button { self.base.id() } - /// Sets the visual style of the button using a [ButtonStyle]. + /// Sets the visual style of the button using a [`ButtonStyle`]. fn style(mut self, style: ButtonStyle) -> Self { self.base = self.base.style(style); self } - /// Sets the button's size using a [ButtonSize]. + /// Sets the button's size using a [`ButtonSize`]. fn size(mut self, size: ButtonSize) -> Self { self.base = self.base.size(size); self @@ -297,7 +297,7 @@ impl ButtonCommon for Button { /// Sets a tooltip for the button. /// /// This method allows a tooltip to be set for the button. The tooltip is a function that - /// takes a mutable reference to a [WindowContext] and returns an [AnyView]. The tooltip + /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip /// is displayed when the user hovers over the button. /// /// # Examples diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 2c292595c751c136b4c83b4832f5b49dda81c17a..3e4b478a9a237c44b95e5de09158e35e40e55449 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -4,7 +4,7 @@ use smallvec::SmallVec; use crate::prelude::*; -/// A trait for buttons that can be Selected. Enables setting the [ButtonStyle] of a button when it is selected. +/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected. pub trait SelectableButton: Selectable { fn selected_style(self, style: ButtonStyle) -> Self; } @@ -95,7 +95,7 @@ impl From for Color { } } -/// Sets the visual appearance of a button. +/// The visual appearance of a button. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { /// A filled button with a solid background color. Provides emphasis versus @@ -263,7 +263,9 @@ impl ButtonStyle { } } -/// Sets the height of a button. Can also be used to size non-button elements to align with [Button]s. +/// The height of a button. +/// +/// Can also be used to size non-button elements to align with [`Button`]s. #[derive(Default, PartialEq, Clone, Copy)] pub enum ButtonSize { Large, diff --git a/crates/ui/src/components/divider.rs b/crates/ui/src/components/divider.rs index dfe31b677ba6fb8b1d4a2878b97a9ad947856cac..772fc1a81a34b816649efeae19d36fc7405c66b2 100644 --- a/crates/ui/src/components/divider.rs +++ b/crates/ui/src/components/divider.rs @@ -7,7 +7,7 @@ enum DividerDirection { Vertical, } -/// Sets the color of a [Divider]. +/// The color of a [`Divider`]. #[derive(Default)] pub enum DividerColor { Border, diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 8cc43bc99374432188a456e5fb2b6af12d2eb092..403053baf4227fb945df3eccacc6e1a32e95c914 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -47,7 +47,7 @@ impl Label { } impl LabelCommon for Label { - /// Sets the size of the label using a [LabelSize]. + /// Sets the size of the label using a [`LabelSize`]. /// /// # Examples /// @@ -59,7 +59,7 @@ impl LabelCommon for Label { self } - /// Sets the line height style of the label using a [LineHeightStyle]. + /// Sets the line height style of the label using a [`LineHeightStyle`]. /// /// # Examples /// @@ -71,7 +71,7 @@ impl LabelCommon for Label { self } - /// Sets the color of the label using a [Color]. + /// Sets the color of the label using a [`Color`]. /// /// # Examples /// diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 6bfc88bbec3ef292d1163717b0f1aaad7ec2c2fb..52c907fab51f5cf07e92e42aaedfe055de995247 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -108,7 +108,7 @@ impl PopoverMenu { } } -/// Creates a [PopoverMenu] +/// Creates a [`PopoverMenu`] pub fn popover_menu(id: impl Into) -> PopoverMenu { PopoverMenu { id: id.into(), diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 282884c948b757b9ba305e8ee5532e7d5732796e..cbc924ff59936d4904695cda12d253a28610af36 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -39,7 +39,7 @@ impl RightClickMenu { } } -/// Creates a [RightClickMenu] +/// Creates a [`RightClickMenu`] pub fn right_click_menu(id: impl Into) -> RightClickMenu { RightClickMenu { id: id.into(), diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index ee635e4d6b944d807034087ff1e4de2788505847..69d1d0583d70e8176f577ce7d7fa651845df82f6 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -1,4 +1,4 @@ -//! The prelude of this crate. When building UI in zed you almost always want to import this. +//! The prelude of this crate. When building UI in Zed you almost always want to import this. pub use gpui::prelude::*; pub use gpui::{ diff --git a/crates/ui/src/selectable.rs b/crates/ui/src/selectable.rs index e5e60008b9194d129fb03523ead8e3d58c804681..54da86d09492f03a106cb1be265c0a46851000f6 100644 --- a/crates/ui/src/selectable.rs +++ b/crates/ui/src/selectable.rs @@ -1,4 +1,6 @@ -/// A trait for elements that can be selected. Generally used to enable "toggle" or "active" behavior and styles on an element through the [Selection] status. +/// A trait for elements that can be selected. +/// +/// Generally used to enable "toggle" or "active" behavior and styles on an element through the [`Selection`] status. pub trait Selectable { /// Sets whether the element is selected. fn selected(self, selected: bool) -> Self; diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 1f47fe4f140cfc0e0766ecf5a028c382a8d22b73..b2eaf75ed913b753574be6f449133eb536bd0a72 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -14,7 +14,7 @@ fn elevated(this: E, cx: &mut WindowContext, index: ElevationIndex) - .shadow(index.shadow()) } -/// Extends [gpui::Styled] with Zed specific styling methods. +/// Extends [`gpui::Styled`] with Zed-specific styling methods. pub trait StyledExt: Styled + Sized { /// Horizontally stacks elements. /// @@ -30,7 +30,7 @@ pub trait StyledExt: Styled + Sized { self.flex().flex_col() } - /// Sets the text size using a [UiTextSize]. + /// Sets the text size using a [`UiTextSize`]. fn text_ui_size(self, size: UiTextSize) -> Self { self.text_size(size.rems()) } @@ -79,7 +79,7 @@ pub trait StyledExt: Styled + Sized { self.text_size(settings.buffer_font_size(cx)) } - /// The [`Surface`](ui::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements + /// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// @@ -88,7 +88,7 @@ pub trait StyledExt: Styled + Sized { elevated(self, cx, ElevationIndex::Surface) } - /// Non-Modal Elevated Surfaces appear above the [`Surface`](ui::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc. + /// Non-Modal Elevated Surfaces appear above the [`Surface`](ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc. /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// @@ -101,7 +101,7 @@ pub trait StyledExt: Styled + Sized { /// /// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal. /// - /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui::ElevationIndex::ElevatedSurface) layer. + /// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ElevationIndex::ElevatedSurface) layer. /// /// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()` /// diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index 055a419e072afa959e12fe2d2f5b4942b34679a1..ec1848ca6093606aea9abcc926f242b20e0e727c 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -85,7 +85,7 @@ impl LayerIndex { } } -/// Sets ann appropriate z-index for the given layer based on it's intended useage. +/// An appropriate z-index for the given layer based on its intended useage. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementIndex { Effect, diff --git a/crates/ui/src/styles/typography.rs b/crates/ui/src/styles/typography.rs index 20f5e5f48f3815559d3ba48cdbe27ea6d242ebfb..70cd797d5162b534a9ae42a0124f81c34342716e 100644 --- a/crates/ui/src/styles/typography.rs +++ b/crates/ui/src/styles/typography.rs @@ -38,7 +38,7 @@ impl UiTextSize { } } -/// Sets the size of a [Headline] element +/// The size of a [`Headline`] element #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum HeadlineSize { XSmall, diff --git a/crates/ui/src/utils/format_distance.rs b/crates/ui/src/utils/format_distance.rs index 17f6b7a6549b793b6fe58c7a00c083d2dbe53c42..3582d87f70ad37dc3bd710ef433dc2242bc26d32 100644 --- a/crates/ui/src/utils/format_distance.rs +++ b/crates/ui/src/utils/format_distance.rs @@ -7,10 +7,10 @@ pub enum DateTimeType { } impl DateTimeType { - /// Converts the DateTimeType to a NaiveDateTime. + /// Converts the [`DateTimeType`] to a [`NaiveDateTime`]. /// - /// If the DateTimeType is already a NaiveDateTime, it will be returned as is. - /// If the DateTimeType is a DateTime, it will be converted to a NaiveDateTime. + /// If the [`DateTimeType`] is already a [`NaiveDateTime`], it will be returned as is. + /// If the [`DateTimeType`] is a [`DateTime`], it will be converted to a [`NaiveDateTime`]. pub fn to_naive(&self) -> NaiveDateTime { match self { DateTimeType::Naive(naive) => *naive, @@ -68,13 +68,13 @@ impl FormatDistance { } } -/// Calculates the distance in seconds between two NaiveDateTime objects. +/// Calculates the distance in seconds between two [`NaiveDateTime`] objects. /// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative. /// /// ## Arguments /// -/// * `date` - A NaiveDateTime object representing the date of interest -/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made +/// * `date` - A [NaiveDateTime`] object representing the date of interest +/// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 { let duration = date.signed_duration_since(base_date); -duration.num_seconds() @@ -233,12 +233,12 @@ fn distance_string( /// /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. /// -/// Use [naive_format_distance_from_now] to compare a NaiveDateTime against now. +/// Use [`format_distance_from_now`] to compare a NaiveDateTime against now. /// /// # Arguments /// -/// * `date` - The NaiveDateTime to compare. -/// * `base_date` - The NaiveDateTime to compare against. +/// * `date` - The [`NaiveDateTime`] to compare. +/// * `base_date` - The [`NaiveDateTime`] to compare against. /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future /// @@ -274,7 +274,7 @@ pub fn format_distance( /// /// # Arguments /// -/// * `datetime` - The NaiveDateTime to compare with the current time. +/// * `datetime` - The [`NaiveDateTime`] to compare with the current time. /// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed /// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future /// From 356f9fc3b64a375d0af98c33ebe53bad24bcd539 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:50:00 -0800 Subject: [PATCH 052/334] Store a raw Room pointer on RoomDelegate --- crates/live_kit_client/src/prod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 74cf8423bc57f638d5dc3602f765e4f656fe9df0..df33cf72f0239ef4e2464c664ddf641cd299305f 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -513,14 +513,15 @@ impl Drop for Room { struct RoomDelegate { native_delegate: swift::RoomDelegate, - _weak_room: Weak, + weak_room: *mut c_void, } impl RoomDelegate { fn new(weak_room: Weak) -> Self { + let weak_room = weak_room.into_raw() as *mut c_void; let native_delegate = unsafe { LKRoomDelegateCreate( - weak_room.as_ptr() as *mut c_void, + weak_room, Self::on_did_disconnect, Self::on_did_subscribe_to_remote_audio_track, Self::on_did_unsubscribe_from_remote_audio_track, @@ -532,7 +533,7 @@ impl RoomDelegate { }; Self { native_delegate, - _weak_room: weak_room, + weak_room, } } @@ -647,6 +648,7 @@ impl Drop for RoomDelegate { fn drop(&mut self) { unsafe { CFRelease(self.native_delegate.0); + let _ = Weak::from_raw(self.weak_room); } } } From e3c603f41bc04eb364d2822e7dbc7c3178be347b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 9 Jan 2024 12:54:51 -0800 Subject: [PATCH 053/334] Make RemoteTrackPublication a tuple struct again --- crates/live_kit_client/src/prod.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index df33cf72f0239ef4e2464c664ddf641cd299305f..5d8ef9bf134a61111247d3fac5b94fd87a50caf8 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -723,29 +723,22 @@ impl Drop for LocalTrackPublication { } } -pub struct RemoteTrackPublication { - native_publication: swift::RemoteTrackPublication, -} +pub struct RemoteTrackPublication(swift::RemoteTrackPublication); impl RemoteTrackPublication { pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self { unsafe { CFRetain(native_track_publication.0); } - Self { - native_publication: native_track_publication, - } + Self(native_track_publication) } pub fn sid(&self) -> String { - unsafe { - CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.native_publication)) - .to_string() - } + unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() } } pub fn is_muted(&self) -> bool { - unsafe { LKRemoteTrackPublicationIsMuted(self.native_publication) } + unsafe { LKRemoteTrackPublicationIsMuted(self.0) } } pub fn set_enabled(&self, enabled: bool) -> impl Future> { @@ -763,7 +756,7 @@ impl RemoteTrackPublication { unsafe { LKRemoteTrackPublicationSetEnabled( - self.native_publication, + self.0, enabled, complete_callback, Box::into_raw(Box::new(tx)) as *mut c_void, @@ -776,7 +769,7 @@ impl RemoteTrackPublication { impl Drop for RemoteTrackPublication { fn drop(&mut self) { - unsafe { CFRelease(self.native_publication.0) } + unsafe { CFRelease(self.0 .0) } } } From a46947d5d76d2eddb437d37fc66fc25f766ebc32 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 14:19:48 -0700 Subject: [PATCH 054/334] Fix panic in set_scroll_anchor_remote --- crates/editor/src/scroll.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 0798870f76cb37131b86295b57e5f3f01ad22705..bc5fe4bddd1b38d9445f69bd481145a2f3c884f7 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -384,10 +384,12 @@ impl Editor { ) { hide_hover(self, cx); let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1); - let top_row = scroll_anchor - .anchor - .to_point(&self.buffer().read(cx).snapshot(cx)) - .row; + let snapshot = &self.buffer().read(cx).snapshot(cx); + if !scroll_anchor.anchor.is_valid(snapshot) { + log::warn!("Invalid scroll anchor: {:?}", scroll_anchor); + return; + } + let top_row = scroll_anchor.anchor.to_point(snapshot).row; self.scroll_manager .set_anchor(scroll_anchor, top_row, false, false, workspace_id, cx); } From a579ef17d7cb5b50da80535293683d273a9acc9d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 16:21:27 -0500 Subject: [PATCH 055/334] Rename `Shape` to `AvatarShape` (#3986) This PR renames the `Shape` enum to `AvatarShape`, since it seems pretty specific to `Avatar`s. Release Notes: - N/A --- crates/ui/src/components/avatar.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 98a48de6863af8cfd171dd67cee2604b395261f2..cd85d179951339371faa389b76494ff593b88070 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -1,10 +1,13 @@ use crate::prelude::*; use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled}; +/// The shape of an [`Avatar`]. #[derive(Debug, Default, PartialEq, Clone)] -pub enum Shape { +pub enum AvatarShape { + /// The avatar is shown in a circle. #[default] Circle, + /// The avatar is shown in a rectangle with rounded corners. RoundedRectangle, } @@ -14,7 +17,7 @@ pub enum Shape { /// /// ``` /// Avatar::new("path/to/image.png") -/// .shape(Shape::Circle) +/// .shape(AvatarShape::Circle) /// .grayscale(true) /// .border_color(cx.theme().colors().border) /// ``` @@ -28,7 +31,7 @@ pub struct Avatar { impl RenderOnce for Avatar { fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { if self.image.style().corner_radii.top_left.is_none() { - self = self.shape(Shape::Circle); + self = self.shape(AvatarShape::Circle); } let size = cx.rem_size(); @@ -84,12 +87,12 @@ impl Avatar { /// # Examples /// /// ``` - /// Avatar::new("path/to/image.png").shape(Shape::Circle); + /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); /// ``` - pub fn shape(mut self, shape: Shape) -> Self { + pub fn shape(mut self, shape: AvatarShape) -> Self { self.image = match shape { - Shape::Circle => self.image.rounded_full(), - Shape::RoundedRectangle => self.image.rounded_md(), + AvatarShape::Circle => self.image.rounded_full(), + AvatarShape::RoundedRectangle => self.image.rounded_md(), }; self } From 8b71b1d07b2bfc9b7cf2dfaf66e16bb7d15e4e06 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 23:27:54 +0200 Subject: [PATCH 056/334] Do not dismiss buffer search when any modal is present Co-authored-by: Piotr Osiewicz --- crates/search/src/buffer_search.rs | 13 +++++++++---- crates/workspace/src/modal_layer.rs | 4 ++++ crates/workspace/src/workspace.rs | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 9b2719936080fd14ba65c8f0b044c11455815f2c..7dd4b85231695d8ece48dbd6075cd030c2081dfc 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -43,7 +43,7 @@ pub enum Event { } pub fn init(cx: &mut AppContext) { - cx.observe_new_views(|editor: &mut Workspace, _| BufferSearchBar::register(editor)) + cx.observe_new_views(|workspace: &mut Workspace, _| BufferSearchBar::register(workspace)) .detach(); } @@ -479,6 +479,11 @@ impl SearchActionsRegistrar for Workspace { callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ) { self.register_action(move |workspace, action: &A, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + let pane = workspace.active_pane(); pane.update(cx, move |this, cx| { this.toolbar().update(cx, move |this, cx| { @@ -539,11 +544,11 @@ impl BufferSearchBar { this.select_all_matches(action, cx); }); registrar.register_handler(|this, _: &editor::Cancel, cx| { - if !this.dismissed { + if this.dismissed { + cx.propagate(); + } else { this.dismiss(&Dismiss, cx); - return; } - cx.propagate(); }); registrar.register_handler(|this, deploy, cx| { this.deploy(deploy, cx); diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index ae105345cd360eb072e4a1064efbcca339bfb954..627581c4760c0209de3379d5a2cbf0ead7cbbc62 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -101,6 +101,10 @@ impl ModalLayer { let active_modal = self.active_modal.as_ref()?; active_modal.modal.view().downcast::().ok() } + + pub fn has_active_modal(&self) -> bool { + self.active_modal.is_some() + } } impl Render for ModalLayer { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 739ce88636ca32a88be1496103ad03ed42f63808..09e0a1378d440e0bbf2eb2a7dc21ec88557fa320 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3383,6 +3383,10 @@ impl Workspace { div } + pub fn has_active_modal(&self, cx: &WindowContext<'_>) -> bool { + self.modal_layer.read(cx).has_active_modal() + } + pub fn active_modal( &mut self, cx: &ViewContext, From 684bd530f0a2d78b9b5c37c72b83fba09e02f451 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 16:56:28 -0500 Subject: [PATCH 057/334] ui: Fix doc tests (#3989) There were a ton of doc tests that weren't compiling in the `ui` crate, so this PR fixes them. Release Notes: - N/A --- crates/ui/src/components/avatar.rs | 8 +++- crates/ui/src/components/button/button.rs | 33 ++++++++++++--- crates/ui/src/components/label/label.rs | 26 +++++++++--- crates/ui/src/components/label/label_like.rs | 7 ++++ crates/ui/src/utils/format_distance.rs | 42 -------------------- 5 files changed, 63 insertions(+), 53 deletions(-) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index cd85d179951339371faa389b76494ff593b88070..9e64e1223c346f4d153fb7d2955b606ff53736ae 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -16,10 +16,12 @@ pub enum AvatarShape { /// # Examples /// /// ``` +/// use ui::{Avatar, AvatarShape}; +/// /// Avatar::new("path/to/image.png") /// .shape(AvatarShape::Circle) /// .grayscale(true) -/// .border_color(cx.theme().colors().border) +/// .border_color(gpui::red()); /// ``` #[derive(IntoElement)] pub struct Avatar { @@ -87,6 +89,8 @@ impl Avatar { /// # Examples /// /// ``` + /// use ui::{Avatar, AvatarShape}; + /// /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); /// ``` pub fn shape(mut self, shape: AvatarShape) -> Self { @@ -102,6 +106,8 @@ impl Avatar { /// # Examples /// /// ``` + /// use ui::{Avatar, AvatarShape}; + /// /// let avatar = Avatar::new("path/to/image.png").grayscale(true); /// ``` pub fn grayscale(mut self, grayscale: bool) -> Self { diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index b241b519cdab78c08168eeb1174ab24cc067fc5e..fcc30e633815fd764909c32bb7bbc34b8c5e0624 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -23,6 +23,8 @@ use super::button_icon::ButtonIcon; /// indicates what action will be performed when the button is clicked. /// /// ``` +/// use ui::prelude::*; +/// /// Button::new("button_id", "Click me!") /// .on_click(|event, cx| { /// // Handle click event @@ -34,9 +36,11 @@ use super::button_icon::ButtonIcon; /// a trigger for a popover menu, where clicking the button toggles the visibility of the menu. /// /// ``` +/// use ui::prelude::*; +/// /// Button::new("button_id", "Click me!") /// .icon(IconName::Check) -/// .selected(some_bool) +/// .selected(true) /// .on_click(|event, cx| { /// // Handle click event /// }); @@ -45,8 +49,11 @@ use super::button_icon::ButtonIcon; /// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method. /// /// ``` +/// use ui::prelude::*; +/// use ui::TintColor; +/// /// Button::new("button_id", "Click me!") -/// .selected(some_bool) +/// .selected(true) /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) /// .on_click(|event, cx| { /// // Handle click event @@ -58,6 +65,8 @@ use super::button_icon::ButtonIcon; /// The button's content, including text and icons, is centered by default. /// /// ``` +/// use ui::prelude::*; +/// /// let button = Button::new("button_id", "Click me!") /// .full_width() /// .on_click(|event, cx| { @@ -167,6 +176,8 @@ impl Selectable for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .selected(true) /// .on_click(|event, cx| { @@ -187,6 +198,9 @@ impl SelectableButton for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// use ui::TintColor; + /// /// Button::new("button_id", "Click me!") /// .selected(true) /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) @@ -210,6 +224,8 @@ impl Disableable for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .disabled(true) /// .on_click(|event, cx| { @@ -244,8 +260,10 @@ impl FixedWidth for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") - /// .width(DefiniteLength::Pixels(100)) + /// .width(px(100.).into()) /// .on_click(|event, cx| { /// // Handle click event /// }); @@ -262,6 +280,8 @@ impl FixedWidth for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// Button::new("button_id", "Click me!") /// .full_width() /// .on_click(|event, cx| { @@ -303,9 +323,12 @@ impl ButtonCommon for Button { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// use ui::Tooltip; + /// /// Button::new("button_id", "Click me!") - /// .tooltip(|cx| { - /// Text::new("This is a tooltip").into() + /// .tooltip(move |cx| { + /// Tooltip::text("This is a tooltip", cx) /// }) /// .on_click(|event, cx| { /// // Handle click event diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs index 403053baf4227fb945df3eccacc6e1a32e95c914..0ba67286a22ccaeec1505612ec34cbc3b1b5883e 100644 --- a/crates/ui/src/components/label/label.rs +++ b/crates/ui/src/components/label/label.rs @@ -10,18 +10,24 @@ use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; /// # Examples /// /// ``` -/// Label::new("Hello, World!") +/// use ui::prelude::*; +/// +/// Label::new("Hello, World!"); /// ``` /// /// **A colored label**, for example labeling a dangerous action: /// /// ``` +/// use ui::prelude::*; +/// /// let my_label = Label::new("Delete").color(Color::Error); /// ``` /// /// **A label with a strikethrough**, for example labeling something that has been deleted: /// /// ``` +/// use ui::prelude::*; +/// /// let my_label = Label::new("Deleted").strikethrough(true); /// ``` #[derive(IntoElement)] @@ -31,11 +37,13 @@ pub struct Label { } impl Label { - /// Create a new `Label` with the given text. + /// Create a new [`Label`] with the given text. /// /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// let my_label = Label::new("Hello, World!"); /// ``` pub fn new(label: impl Into) -> Self { @@ -52,7 +60,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").size(LabelSize::Large); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").size(LabelSize::Small); /// ``` fn size(mut self, size: LabelSize) -> Self { self.base = self.base.size(size); @@ -64,7 +74,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::Normal); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::UiLabel); /// ``` fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { self.base = self.base.line_height_style(line_height_style); @@ -76,7 +88,9 @@ impl LabelCommon for Label { /// # Examples /// /// ``` - /// let my_label = Label::new("Hello, World!").color(Color::Primary); + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").color(Color::Accent); /// ``` fn color(mut self, color: Color) -> Self { self.base = self.base.color(color); @@ -88,6 +102,8 @@ impl LabelCommon for Label { /// # Examples /// /// ``` + /// use ui::prelude::*; + /// /// let my_label = Label::new("Hello, World!").strikethrough(true); /// ``` fn strikethrough(mut self, strikethrough: bool) -> Self { diff --git a/crates/ui/src/components/label/label_like.rs b/crates/ui/src/components/label/label_like.rs index d7bd30187d6d816b01e0192a10845fa53401fe62..6da07d81a39c8a51355b6d922e19c302c1c683ad 100644 --- a/crates/ui/src/components/label/label_like.rs +++ b/crates/ui/src/components/label/label_like.rs @@ -21,9 +21,16 @@ pub enum LineHeightStyle { /// A common set of traits all labels must implement. pub trait LabelCommon { + /// Sets the size of the label using a [`LabelSize`]. fn size(self, size: LabelSize) -> Self; + + /// Sets the line height style of the label using a [`LineHeightStyle`]. fn line_height_style(self, line_height_style: LineHeightStyle) -> Self; + + /// Sets the color of the label using a [`Color`]. fn color(self, color: Color) -> Self; + + /// Sets the strikethrough property of the label. fn strikethrough(self, strikethrough: bool) -> Self; } diff --git a/crates/ui/src/utils/format_distance.rs b/crates/ui/src/utils/format_distance.rs index 3582d87f70ad37dc3bd710ef433dc2242bc26d32..03a0de3adb75c656272397a1f851b8c12ed9eee6 100644 --- a/crates/ui/src/utils/format_distance.rs +++ b/crates/ui/src/utils/format_distance.rs @@ -234,28 +234,6 @@ fn distance_string( /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. /// /// Use [`format_distance_from_now`] to compare a NaiveDateTime against now. -/// -/// # Arguments -/// -/// * `date` - The [`NaiveDateTime`] to compare. -/// * `base_date` - The [`NaiveDateTime`] to compare against. -/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed -/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future -/// -/// # Example -/// -/// ```rust -/// use chrono::DateTime; -/// use ui::utils::format_distance; -/// -/// fn time_between_moon_landings() -> String { -/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local(); -/// let base_date = DateTime::parse_from_rfc3339("1972-12-14T00:00:00Z").unwrap().naive_local(); -/// format!("There was {} between the first and last crewed moon landings.", naive_format_distance(date, base_date, false, false)) -/// } -/// ``` -/// -/// Output: `"There was about 3 years between the first and last crewed moon landings."` pub fn format_distance( date: DateTimeType, base_date: NaiveDateTime, @@ -271,26 +249,6 @@ pub fn format_distance( /// Get the time difference between a date and now as relative human readable string. /// /// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc. -/// -/// # Arguments -/// -/// * `datetime` - The [`NaiveDateTime`] to compare with the current time. -/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed -/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future -/// -/// # Example -/// -/// ```rust -/// use chrono::DateTime; -/// use ui::utils::naive_format_distance_from_now; -/// -/// fn time_since_first_moon_landing() -> String { -/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local(); -/// format!("It's been {} since Apollo 11 first landed on the moon.", naive_format_distance_from_now(date, false, false)) -/// } -/// ``` -/// -/// Output: `It's been over 54 years since Apollo 11 first landed on the moon.` pub fn format_distance_from_now( datetime: DateTimeType, include_seconds: bool, From 80790d921dcd1bea77eabfdd329f9920b40a6b81 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 14:16:46 -0800 Subject: [PATCH 058/334] Fix / remove small todos --- .../collab/src/tests/channel_message_tests.rs | 1 - crates/collab_ui/src/collab_titlebar_item.rs | 6 --- .../incoming_call_notification.rs | 1 - .../project_shared_notification.rs | 2 - crates/editor/src/editor.rs | 4 -- crates/editor/src/editor_tests.rs | 9 ---- crates/editor/src/movement.rs | 2 +- crates/editor/src/scroll/actions.rs | 7 ++- crates/gpui/src/app/test_context.rs | 2 +- crates/gpui/src/elements/overlay.rs | 50 +++++++++++++++++-- crates/text/src/selection.rs | 2 +- 11 files changed, 52 insertions(+), 34 deletions(-) diff --git a/crates/collab/src/tests/channel_message_tests.rs b/crates/collab/src/tests/channel_message_tests.rs index f5da0e3ee6fc85016dafee4b0e6d01c3ec738520..5870bd193842620a2953e577ab0005b48237bcde 100644 --- a/crates/collab/src/tests/channel_message_tests.rs +++ b/crates/collab/src/tests/channel_message_tests.rs @@ -262,7 +262,6 @@ async fn test_remove_channel_message( #[track_caller] fn assert_messages(chat: &Model, messages: &[&str], cx: &mut TestAppContext) { - // todo!(don't directly borrow here) assert_eq!( chat.read_with(cx, |chat, _| { chat.messages() diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index f2106b9a8f4d769801d2eadc7c0259334966b8b6..03dfd450704153b31c52ec3d0baad0d215389190 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -41,12 +41,6 @@ pub fn init(cx: &mut AppContext) { workspace.set_titlebar_item(titlebar_item.into(), cx) }) .detach(); - // todo!() - // cx.add_action(CollabTitlebarItem::share_project); - // cx.add_action(CollabTitlebarItem::unshare_project); - // cx.add_action(CollabTitlebarItem::toggle_user_menu); - // cx.add_action(CollabTitlebarItem::toggle_vcs_menu); - // cx.add_action(CollabTitlebarItem::toggle_project_menu); } pub struct CollabTitlebarItem { diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index 223415119fca00cca478d56423b20f9b44417c2e..93df9a4be5445bd67072affaaf2093e7fd2a32a1 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -19,7 +19,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in notification_windows.drain(..) { window .update(&mut cx, |_, cx| { - // todo!() cx.remove_window(); }) .log_err(); diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 79adc69a801dbe5ed30c7b9afb8d1df19bba6c5e..88fe540c397b65c8ddc3ad0230c47210b8bc0e7e 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -51,7 +51,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in windows { window .update(cx, |_, cx| { - // todo!() cx.remove_window(); }) .ok(); @@ -64,7 +63,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { for window in windows { window .update(cx, |_, cx| { - // todo!() cx.remove_window(); }) .ok(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 71fe6ccfcbc743dc8121362e11f0f9431e5ce05d..95aec1a2007e19df4eb3099bff0f104bd4754a80 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1829,10 +1829,6 @@ impl Editor { this.end_selection(cx); this.scroll_manager.show_scrollbar(cx); - // todo!("use a different mechanism") - // let editor_created_event = EditorCreated(cx.handle()); - // cx.emit_global(editor_created_event); - if mode == EditorMode::Full { let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars(); cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars)); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 66f28db3e463d39d14cff2ab3ab1890e6e98995f..520c3714d3d529dbcd2df4d4cc4d750db2a7a53c 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -539,7 +539,6 @@ fn test_clone(cx: &mut TestAppContext) { ); } -//todo!(editor navigate) #[gpui::test] async fn test_navigation_history(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -993,7 +992,6 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) { }); } -//todo!(finish editor tests) #[gpui::test] fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -1259,7 +1257,6 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { }); } -//todo!(finish editor tests) #[gpui::test] fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -1318,7 +1315,6 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { }); } -//todo!(simulate_resize) #[gpui::test] async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -2546,7 +2542,6 @@ fn test_delete_line(cx: &mut TestAppContext) { }); } -//todo!(select_anchor_ranges) #[gpui::test] fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -3114,7 +3109,6 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { }); } -//todo!(test_transpose) #[gpui::test] fn test_transpose(cx: &mut TestAppContext) { init_test(cx, |_| {}); @@ -4860,7 +4854,6 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { }); } -// todo!(select_anchor_ranges) #[gpui::test] async fn test_snippets(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -6455,7 +6448,6 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) { }); } -// todo!(following) #[gpui::test] async fn test_following(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -7094,7 +7086,6 @@ async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) { ); } -// todo!(completions) #[gpui::test(iterations = 10)] async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) { // flaky diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 0b13e25d5dd621f9d62fcf05e7d75657a3902656..72441974c3788d659084c19503ffc22740bfd005 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -95,7 +95,7 @@ pub fn up_by_rows( text_layout_details: &TextLayoutDetails, ) -> (DisplayPoint, SelectionGoal) { let mut goal_x = match goal { - SelectionGoal::HorizontalPosition(x) => x.into(), // todo!("Can the fields in SelectionGoal by Pixels? We should extract a geometry crate and depend on that.") + SelectionGoal::HorizontalPosition(x) => x.into(), SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(), SelectionGoal::HorizontalRange { end, .. } => end.into(), _ => map.x_for_display_point(start, text_layout_details), diff --git a/crates/editor/src/scroll/actions.rs b/crates/editor/src/scroll/actions.rs index 21a4258f6f186700128e081b1ab5d36c3a5938c7..436a0291d020f00c3c685d72e5e7c50934158b27 100644 --- a/crates/editor/src/scroll/actions.rs +++ b/crates/editor/src/scroll/actions.rs @@ -11,10 +11,9 @@ impl Editor { return; } - // todo!() - // if self.mouse_context_menu.read(cx).visible() { - // return None; - // } + if self.mouse_context_menu.is_some() { + return; + } if matches!(self.mode, EditorMode::SingleLine) { cx.propagate(); diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index de31339b8d79bb3b4a80d4e8c9bc95834ffef92c..032d0a7963bc8d6bd2e78a156e97b792cac39b8c 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -481,7 +481,7 @@ impl View { use postage::prelude::{Sink as _, Stream as _}; let (tx, mut rx) = postage::mpsc::channel(1024); - let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration(); + let timeout_duration = Duration::from_millis(100); let mut cx = cx.app.borrow_mut(); let subscriptions = ( diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 6772baa2f9c117a834a716efe4d5e733948ce3b3..019436493d937bdc5898db695622f53ff9d8f997 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -15,8 +15,7 @@ pub struct Overlay { anchor_corner: AnchorCorner, fit_mode: OverlayFitMode, anchor_position: Option>, - // todo!(); - // position_mode: OverlayPositionMode, + position_mode: OverlayPositionMode, } /// overlay gives you a floating element that will avoid overflowing the window bounds. @@ -27,6 +26,7 @@ pub fn overlay() -> Overlay { anchor_corner: AnchorCorner::TopLeft, fit_mode: OverlayFitMode::SwitchAnchor, anchor_position: None, + position_mode: OverlayPositionMode::Window, } } @@ -44,6 +44,14 @@ impl Overlay { self } + /// Sets the position mode for this overlay. Local will have this + /// interpret it's [Overlay::position] as relative to the parent element. + /// While Window will have it interpret the position as relative to the window. + pub fn position_mode(mut self, mode: OverlayPositionMode) -> Self { + self.position_mode = mode; + self + } + /// Snap to window edge instead of switching anchor corner when an overflow would occur. pub fn snap_to_window(mut self) -> Self { self.fit_mode = OverlayFitMode::SnapToWindow; @@ -100,9 +108,14 @@ impl Element for Overlay { child_max = child_max.max(&child_bounds.lower_right()); } let size: Size = (child_max - child_min).into(); - let origin = self.anchor_position.unwrap_or(bounds.origin); - let mut desired = self.anchor_corner.get_bounds(origin, size); + let (origin, mut desired) = self.position_mode.get_position_and_bounds( + self.anchor_position, + self.anchor_corner, + size, + bounds, + ); + let limits = Bounds { origin: Point::default(), size: cx.viewport_size(), @@ -184,6 +197,35 @@ pub enum OverlayFitMode { SwitchAnchor, } +#[derive(Copy, Clone, PartialEq)] +pub enum OverlayPositionMode { + Window, + Local, +} + +impl OverlayPositionMode { + fn get_position_and_bounds( + &self, + anchor_position: Option>, + anchor_corner: AnchorCorner, + size: Size, + bounds: Bounds, + ) -> (Point, Bounds) { + match self { + OverlayPositionMode::Window => { + let anchor_position = anchor_position.unwrap_or_else(|| bounds.origin); + let bounds = anchor_corner.get_bounds(anchor_position, size); + (anchor_position, bounds) + } + OverlayPositionMode::Local => { + let anchor_position = anchor_position.unwrap_or_default(); + let bounds = anchor_corner.get_bounds(bounds.origin + anchor_position, size); + (anchor_position, bounds) + } + } + } +} + #[derive(Clone, Copy, PartialEq, Eq)] pub enum AnchorCorner { TopLeft, diff --git a/crates/text/src/selection.rs b/crates/text/src/selection.rs index 4f1f9a29223b927cd993d53a9df27488c9b37ad0..480cb99d747783b7c7bfc100af8b57401781a984 100644 --- a/crates/text/src/selection.rs +++ b/crates/text/src/selection.rs @@ -5,7 +5,7 @@ use std::ops::Range; #[derive(Copy, Clone, Debug, PartialEq)] pub enum SelectionGoal { None, - HorizontalPosition(f32), // todo!("Can we use pixels here without adding a runtime gpui dependency?") + HorizontalPosition(f32), HorizontalRange { start: f32, end: f32 }, WrappedHorizontalPosition((u32, f32)), } From ed263a7b5cccd8e715125fe8e3cc03ce5c9ce485 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 15:08:05 -0800 Subject: [PATCH 059/334] Resolve more todos --- crates/gpui/src/app/test_context.rs | 11 ++++++++-- crates/gpui/src/elements/div.rs | 17 +++------------ crates/gpui/src/elements/text.rs | 5 ++++- crates/gpui/src/style.rs | 3 ++- crates/gpui/src/test.rs | 1 - crates/gpui/src/text_system.rs | 24 ++++++++++++++++++--- crates/gpui/src/text_system/line_wrapper.rs | 2 +- crates/gpui_macros/src/test.rs | 13 +++++------ 8 files changed, 47 insertions(+), 29 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 032d0a7963bc8d6bd2e78a156e97b792cac39b8c..ddf88537579809f9c5c2d22995d8b1379812c789 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -25,6 +25,7 @@ pub struct TestAppContext { pub dispatcher: TestDispatcher, test_platform: Rc, text_system: Arc, + fn_name: Option<&'static str>, } impl Context for TestAppContext { @@ -85,7 +86,7 @@ impl Context for TestAppContext { impl TestAppContext { /// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you. - pub fn new(dispatcher: TestDispatcher) -> Self { + pub fn new(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self { let arc_dispatcher = Arc::new(dispatcher.clone()); let background_executor = BackgroundExecutor::new(arc_dispatcher.clone()); let foreground_executor = ForegroundExecutor::new(arc_dispatcher); @@ -101,12 +102,18 @@ impl TestAppContext { dispatcher: dispatcher.clone(), test_platform: platform, text_system, + fn_name, } } + /// The name of the test function that created this `TestAppContext` + pub fn test_function_name(&self) -> Option<&'static str> { + self.fn_name + } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { - Self::new(self.dispatcher.clone()) + Self::new(self.dispatcher.clone(), self.fn_name) } /// Simulates quitting the app. diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 45097411d13875c7b1a8fecc4888711dbc0d9dd3..627a2ac339d631c666926d4fc8e1354396cb36f7 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1003,7 +1003,7 @@ impl Interactivity { if let Some(text) = cx .text_system() .shape_text( - &element_id, + element_id.into(), FONT_SIZE, &[cx.text_style().to_run(str_len)], None, @@ -1055,22 +1055,11 @@ impl Interactivity { }; eprintln!( - "This element is created at:\n{}:{}:{}", - location.file(), + "This element was created at:\n{}:{}:{}", + dir.join(location.file()).to_string_lossy(), location.line(), location.column() ); - - std::process::Command::new("zed") - .arg(format!( - "{}/{}:{}:{}", - dir.to_string_lossy(), - location.file(), - location.line(), - location.column() - )) - .spawn() - .ok(); } } }); diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 29c93fd19e91518a96a5f663352b92519264aca9..4e5c6721472398233a457573a56d27d43881c071 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -202,7 +202,10 @@ impl TextState { let Some(lines) = cx .text_system() .shape_text( - &text, font_size, &runs, wrap_width, // Wrap if we know the width. + text.clone(), + font_size, + &runs, + wrap_width, // Wrap if we know the width. ) .log_err() else { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 244ccebf2498fb9ff275d0818215a9ba658ffc02..a21957611d09feb25f59d4a842ab9b998e83b4d4 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -165,7 +165,8 @@ impl Default for TextStyle { fn default() -> Self { TextStyle { color: black(), - font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system") + // Helvetica is a web safe font, so it should be available + font_family: "Helvetica".into(), font_features: FontFeatures::default(), font_size: rems(1.).into(), line_height: phi(), diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 1771f29c67b13639fbb7b87029840c435b6a681d..f53d19fdc8b1028bdddba0957719e05b3af4d69d 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -39,7 +39,6 @@ pub fn run_test( max_retries: usize, test_fn: &mut (dyn RefUnwindSafe + Fn(TestDispatcher, u64)), on_fail_fn: Option, - _fn_name: String, // todo!("re-enable fn_name") ) { let starting_seed = env::var("SEED") .map(|seed| seed.parse().expect("invalid SEED variable")) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 0969560e95d62e6d74dc82e88eb4b13958a77480..47073bcde0ef77b7b6674d0c0a24b7dfa107e948 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -258,7 +258,7 @@ impl TextSystem { pub fn shape_text( &self, - text: &str, // todo!("pass a SharedString and preserve it when passed a single line?") + text: SharedString, font_size: Pixels, runs: &[TextRun], wrap_width: Option, @@ -268,8 +268,8 @@ impl TextSystem { let mut lines = SmallVec::new(); let mut line_start = 0; - for line_text in text.split('\n') { - let line_text = SharedString::from(line_text.to_string()); + + let mut process_line = |line_text: SharedString| { let line_end = line_start + line_text.len(); let mut last_font: Option = None; @@ -335,6 +335,24 @@ impl TextSystem { } font_runs.clear(); + }; + + let mut split_lines = text.split('\n'); + let mut processed = false; + + if let Some(first_line) = split_lines.next() { + if let Some(second_line) = split_lines.next() { + processed = true; + process_line(first_line.to_string().into()); + process_line(second_line.to_string().into()); + for line_text in split_lines { + process_line(line_text.to_string().into()); + } + } + } + + if !processed { + process_line(text); } self.font_runs_pool.lock().push(font_runs); diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index e2f0a8a5fdbce9c0e3f81c9fb018bdf517b1db7c..79013adbb2c83cd0e8e816ca5ef7bb88618b5ae8 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -143,7 +143,7 @@ mod tests { #[test] fn test_wrap_line() { let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0)); - let cx = TestAppContext::new(dispatcher); + let cx = TestAppContext::new(dispatcher, None); cx.update(|cx| { let text_system = cx.text_system().clone(); diff --git a/crates/gpui_macros/src/test.rs b/crates/gpui_macros/src/test.rs index 70c6da22d5a7ab888f586d87f2d8ad261089d1bf..ee3f8f713701cfc334a1343902edf29fddd781d2 100644 --- a/crates/gpui_macros/src/test.rs +++ b/crates/gpui_macros/src/test.rs @@ -106,7 +106,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname = format_ident!("cx_{}", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)), ); )); cx_teardowns.extend(quote!( @@ -140,8 +141,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { executor.block_test(#inner_fn_name(#inner_fn_args)); #cx_teardowns }, - #on_failure_fn_name, - stringify!(#outer_fn_name).to_string(), + #on_failure_fn_name ); } } @@ -169,7 +169,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname_lock = format_ident!("cx_{}_lock", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)) ); let mut #cx_varname_lock = #cx_varname.app.borrow_mut(); )); @@ -186,7 +187,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { let cx_varname = format_ident!("cx_{}", ix); cx_vars.extend(quote!( let mut #cx_varname = gpui::TestAppContext::new( - dispatcher.clone() + dispatcher.clone(), + Some(stringify!(#outer_fn_name)) ); )); cx_teardowns.extend(quote!( @@ -222,7 +224,6 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { #cx_teardowns }, #on_failure_fn_name, - stringify!(#outer_fn_name).to_string(), ); } } From 4da9d61a4264604dd22427fe0e1a40050b85558b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 16:10:12 -0700 Subject: [PATCH 060/334] Implement live kit promotion/demotion --- crates/call/src/room.rs | 30 +++++++++ crates/collab/src/db/ids.rs | 2 +- crates/collab/src/db/queries/projects.rs | 2 +- crates/collab/src/rpc.rs | 45 ++++++++++--- .../collab/src/tests/channel_guest_tests.rs | 27 +++++++- crates/collab_ui/src/collab_panel.rs | 67 ++++++++++++++----- crates/live_kit_client/src/test.rs | 61 ++++++++++++++--- crates/live_kit_server/src/api.rs | 29 ++++++++ crates/live_kit_server/src/live_kit_server.rs | 2 +- 9 files changed, 223 insertions(+), 42 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 0a599e85cf4148fd08d902d611d1f3d70b336d39..3d1f1e70c74ab481864884e37a33e6db03ba5b05 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -754,6 +754,18 @@ impl Room { if this.local_participant.role != role { this.local_participant.role = role; + if role == proto::ChannelRole::Guest { + for project in mem::take(&mut this.shared_projects) { + if let Some(project) = project.upgrade() { + this.unshare_project(project, cx).log_err(); + } + } + this.local_participant.projects.clear(); + if let Some(live_kit_room) = &mut this.live_kit { + live_kit_room.stop_publishing(cx); + } + } + this.joined_projects.retain(|project| { if let Some(project) = project.upgrade() { project.update(cx, |project, cx| project.set_role(role, cx)); @@ -1632,6 +1644,24 @@ impl LiveKitRoom { Ok((result, old_muted)) } + + fn stop_publishing(&mut self, cx: &mut ModelContext) { + if let LocalTrack::Published { + track_publication, .. + } = mem::replace(&mut self.microphone_track, LocalTrack::None) + { + self.room.unpublish_track(track_publication); + cx.notify(); + } + + if let LocalTrack::Published { + track_publication, .. + } = mem::replace(&mut self.screen_track, LocalTrack::None) + { + self.room.unpublish_track(track_publication); + cx.notify(); + } + } } enum LocalTrack { diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 9f77225fb704c1d4d53051cb7ee29d77c3536f80..9dbbfaff8f8d0e94ea56b02eda37485ddc695fa8 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -133,7 +133,7 @@ impl ChannelRole { } } - pub fn can_share_projects(&self) -> bool { + pub fn can_publish_to_rooms(&self) -> bool { use ChannelRole::*; match self { Admin | Member => true, diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 6e1bf16309bd69315903a37ce7b57d389e749161..a55345dc16b331a124736962c456b1114e089b8e 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -49,7 +49,7 @@ impl Database { if !participant .role .unwrap_or(ChannelRole::Member) - .can_share_projects() + .can_publish_to_rooms() { return Err(anyhow!("guests cannot share projects"))?; } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 766cf4bcc0d7344c0780c0a34b62ac4feeb5ea84..b933ce7c9a73a4e801acf349902125f150da2ebe 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1264,18 +1264,41 @@ async fn set_room_participant_role( response: Response, session: Session, ) -> Result<()> { - let room = session - .db() - .await - .set_room_participant_role( - session.user_id, - RoomId::from_proto(request.room_id), - UserId::from_proto(request.user_id), - ChannelRole::from(request.role()), - ) - .await?; + let (live_kit_room, can_publish) = { + let room = session + .db() + .await + .set_room_participant_role( + session.user_id, + RoomId::from_proto(request.room_id), + UserId::from_proto(request.user_id), + ChannelRole::from(request.role()), + ) + .await?; + + let live_kit_room = room.live_kit_room.clone(); + let can_publish = ChannelRole::from(request.role()).can_publish_to_rooms(); + room_updated(&room, &session.peer); + (live_kit_room, can_publish) + }; + + if let Some(live_kit) = session.live_kit_client.as_ref() { + live_kit + .update_participant( + live_kit_room.clone(), + request.user_id.to_string(), + live_kit_server::proto::ParticipantPermission { + can_subscribe: true, + can_publish, + can_publish_data: can_publish, + hidden: false, + recorder: false, + }, + ) + .await + .trace_err(); + } - room_updated(&room, &session.peer); response.send(proto::Ack {})?; Ok(()) } diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 8d21db678bed364cd8504c839bb3c15b339b1cd0..9b68ce3922ab24726130c356636dae7d6899ef35 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -1,7 +1,7 @@ use crate::tests::TestServer; use call::ActiveCall; use editor::Editor; -use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext}; +use gpui::{BackgroundExecutor, TestAppContext}; use rpc::proto; #[gpui::test] @@ -132,5 +132,28 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await - .unwrap() + .unwrap(); + + // B is demoted + active_call_a + .update(cx_a, |call, cx| { + call.room().unwrap().update(cx, |room, cx| { + room.set_participant_role( + client_b.user_id().unwrap(), + proto::ChannelRole::Guest, + cx, + ) + }) + }) + .await + .unwrap(); + cx_a.run_until_parked(); + + // project and buffers are no longer editable + assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); + assert!(editor_b.update(cx_b, |editor, cx| editor.read_only(cx))); + assert!(room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .is_err()); } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index e25ad0d225680e8454ae69ce063a1fb5d67d1b2a..687c9e45b6c4ce6d41a40725c52b5bc6c0f4efba 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -865,9 +865,9 @@ impl CollabPanel { .ok(); })) }) - .when(is_call_admin && role == proto::ChannelRole::Guest, |el| { + .when(is_call_admin, |el| { el.on_secondary_mouse_down(cx.listener(move |this, event: &MouseDownEvent, cx| { - this.deploy_participant_context_menu(event.position, user_id, cx) + this.deploy_participant_context_menu(event.position, user_id, role, cx) })) }) } @@ -1006,27 +1006,60 @@ impl CollabPanel { &mut self, position: Point, user_id: u64, + role: proto::ChannelRole, cx: &mut ViewContext, ) { let this = cx.view().clone(); + if !(role == proto::ChannelRole::Guest || role == proto::ChannelRole::Member) { + return; + } let context_menu = ContextMenu::build(cx, |context_menu, cx| { - context_menu.entry( - "Allow Write Access", - None, - cx.handler_for(&this, move |_, cx| { - ActiveCall::global(cx) - .update(cx, |call, cx| { - let Some(room) = call.room() else { - return Task::ready(Ok(())); - }; - room.update(cx, |room, cx| { - room.set_participant_role(user_id, proto::ChannelRole::Member, cx) + if role == proto::ChannelRole::Guest { + context_menu.entry( + "Grant Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role( + user_id, + proto::ChannelRole::Member, + cx, + ) + }) }) - }) - .detach_and_notify_err(cx) - }), - ) + .detach_and_notify_err(cx) + }), + ) + } else if role == proto::ChannelRole::Member { + context_menu.entry( + "Revoke Write Access", + None, + cx.handler_for(&this, move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| { + let Some(room) = call.room() else { + return Task::ready(Ok(())); + }; + room.update(cx, |room, cx| { + room.set_participant_role( + user_id, + proto::ChannelRole::Guest, + cx, + ) + }) + }) + .detach_and_notify_err(cx) + }), + ) + } else { + unreachable!() + } }); cx.focus_view(&context_menu); diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 07a12a4ab6f5e39df0b672d8fb589497a765fd04..4575fdd2c1845c53b29876d83f1fb6a8498717dc 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; use gpui::BackgroundExecutor; -use live_kit_server::token; +use live_kit_server::{proto, token}; use media::core_video::CVImageBuffer; use parking_lot::Mutex; use postage::watch; @@ -151,6 +151,21 @@ impl TestServer { Ok(()) } + async fn update_participant( + &self, + room_name: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()> { + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + room.participant_permissions.insert(identity, permission); + Ok(()) + } + pub async fn disconnect_client(&self, client_identity: String) { self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -167,15 +182,22 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); - if claims.video.can_publish == Some(false) { - return Err(anyhow!("user is not allowed to publish")); - } - let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + let can_publish = room + .participant_permissions + .get(&identity) + .map(|permission| permission.can_publish) + .or(claims.video.can_publish) + .unwrap_or(true); + + if !can_publish { + return Err(anyhow!("user is not allowed to publish")); + } + let track = Arc::new(RemoteVideoTrack { sid: nanoid::nanoid!(17), publisher_id: identity.clone(), @@ -209,15 +231,22 @@ impl TestServer { let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); - if claims.video.can_publish == Some(false) { - return Err(anyhow!("user is not allowed to publish")); - } - let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + let can_publish = room + .participant_permissions + .get(&identity) + .map(|permission| permission.can_publish) + .or(claims.video.can_publish) + .unwrap_or(true); + + if !can_publish { + return Err(anyhow!("user is not allowed to publish")); + } + let track = Arc::new(RemoteAudioTrack { sid: nanoid::nanoid!(17), publisher_id: identity.clone(), @@ -273,6 +302,7 @@ struct TestServerRoom { client_rooms: HashMap>, video_tracks: Vec>, audio_tracks: Vec>, + participant_permissions: HashMap, } impl TestServerRoom {} @@ -305,6 +335,19 @@ impl live_kit_server::api::Client for TestApiClient { Ok(()) } + async fn update_participant( + &self, + room: String, + identity: String, + permission: live_kit_server::proto::ParticipantPermission, + ) -> Result<()> { + let server = TestServer::get(&self.url)?; + server + .update_participant(room, identity, permission) + .await?; + Ok(()) + } + fn room_token(&self, room: &str, identity: &str) -> Result { let server = TestServer::get(&self.url)?; token::create( diff --git a/crates/live_kit_server/src/api.rs b/crates/live_kit_server/src/api.rs index 2c1e174fb41551d22b498b75d9ce36011ca5a5a9..e7e933c9c39d51d9611da135edd3749030444263 100644 --- a/crates/live_kit_server/src/api.rs +++ b/crates/live_kit_server/src/api.rs @@ -11,10 +11,18 @@ pub trait Client: Send + Sync { async fn create_room(&self, name: String) -> Result<()>; async fn delete_room(&self, name: String) -> Result<()>; async fn remove_participant(&self, room: String, identity: String) -> Result<()>; + async fn update_participant( + &self, + room: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()>; fn room_token(&self, room: &str, identity: &str) -> Result; fn guest_token(&self, room: &str, identity: &str) -> Result; } +pub struct LiveKitParticipantUpdate {} + #[derive(Clone)] pub struct LiveKitClient { http: reqwest::Client, @@ -131,6 +139,27 @@ impl Client for LiveKitClient { Ok(()) } + async fn update_participant( + &self, + room: String, + identity: String, + permission: proto::ParticipantPermission, + ) -> Result<()> { + let _: proto::ParticipantInfo = self + .request( + "twirp/livekit.RoomService/UpdateParticipant", + token::VideoGrant::to_admin(&room), + proto::UpdateParticipantRequest { + room: room.clone(), + identity, + metadata: "".to_string(), + permission: Some(permission), + }, + ) + .await?; + Ok(()) + } + fn room_token(&self, room: &str, identity: &str) -> Result { token::create( &self.key, diff --git a/crates/live_kit_server/src/live_kit_server.rs b/crates/live_kit_server/src/live_kit_server.rs index 7471a96ec418a5ddeeb25d527b98865c31e75686..aa7c1f2fd0179e9062a5165bf4e332e74bf30ea7 100644 --- a/crates/live_kit_server/src/live_kit_server.rs +++ b/crates/live_kit_server/src/live_kit_server.rs @@ -1,3 +1,3 @@ pub mod api; -mod proto; +pub mod proto; pub mod token; From 8cb291baa446a9fcf4d7fcfec16d3dc7d416277e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 9 Jan 2024 18:39:29 -0500 Subject: [PATCH 061/334] Take a different approach to rendering channel buttons (#3991) This PR changes the approach we're using to render the channel buttons to use a more straightforward (and less hacky) approach. ### Motivation Even with the variety of hacks that were employed to make the current approach work, there are still a number of issues with the current solution: - Hovering in the empty space to the left of a channel doesn't correctly apply the hover background to the container for the channel buttons - Hovering to the very right of the collab panel (right on top of the drag handle) causes the channel button container to apply its hover background without applying it to the rest of the row - The buttons would still get pushed off to the right by the indent space in the channel tree, resulting in jagged indicators at small sizes Additionally, the rectangular background placed behind the channel buttons still didn't look great when it overlapped with the channel names. ### Explanation For these reasons, I decided to explore a simpler approach that addresses these issues, albeit with some tradeoffs that I think are acceptable. We now render the absolutely-positioned button container as a sibling element of the `ListItem`. This is to avoid issues with the container getting pushed around based on the contents of the `ListItem` rather than staying absolutely positioned at the end of the row. We also have gotten rid of the background for the button container, and now rely on the background of the individual `IconButton`s to occlude the channel name behind them when the two are overlapping. Here are some examples of the new UI in various configurations: #### When the channel entry is hovered Screenshot 2024-01-09 at 6 15 24 PM #### Overlapping with the channel name Screenshot 2024-01-09 at 6 15 43 PM #### Narrow collab panel Screenshot 2024-01-09 at 6 16 07 PM ### Tradeoffs The new approach comes with the following tradeoffs that I am currently aware of: The occlusion can look a little weird when the icons are in the middle of a channel name (as opposed to fully occluding the end of the channel name): Screenshot 2024-01-09 at 6 24 32 PM Hovering one of the icons causes the icon to be hovered instead of the row: Screenshot 2024-01-09 at 6 31 38 PM Release Notes: - Improved the way channel buttons are displayed. --- crates/collab_ui/src/collab_panel.rs | 139 ++++++++++----------------- 1 file changed, 49 insertions(+), 90 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index df8f2a251fdf4d86ff05f0eaa8052c34317435bb..b9946d6cac97c2c03c8315bc6d59b710516040b4 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2228,47 +2228,6 @@ impl CollabPanel { None }; - let button_container = |cx: &mut ViewContext| { - h_stack() - .absolute() - // We're using a negative coordinate for the right anchor to - // counteract the padding of the `ListItem`. - // - // This prevents a gap from showing up between the background - // of this element and the edge of the collab panel. - .right(rems(-0.5)) - // HACK: Without this the channel name clips on top of the icons, but I'm not sure why. - .z_index(10) - .bg(cx.theme().colors().panel_background) - .when(is_selected || is_active, |this| { - this.bg(cx.theme().colors().ghost_element_selected) - }) - }; - - let messages_button = |cx: &mut ViewContext| { - IconButton::new("channel_chat", IconName::MessageBubbles) - .icon_size(IconSize::Small) - .icon_color(if has_messages_notification { - Color::Default - } else { - Color::Muted - }) - .on_click(cx.listener(move |this, _, cx| this.join_channel_chat(channel_id, cx))) - .tooltip(|cx| Tooltip::text("Open channel chat", cx)) - }; - - let notes_button = |cx: &mut ViewContext| { - IconButton::new("channel_notes", IconName::File) - .icon_size(IconSize::Small) - .icon_color(if has_notes_notification { - Color::Default - } else { - Color::Muted - }) - .on_click(cx.listener(move |this, _, cx| this.open_channel_notes(channel_id, cx))) - .tooltip(|cx| Tooltip::text("Open channel notes", cx)) - }; - let width = self.width.unwrap_or(px(240.)); div() @@ -2326,58 +2285,58 @@ impl CollabPanel { .child( h_stack() .id(channel_id as usize) - // HACK: This is a dirty hack to help with the positioning of the button container. - // - // We're using a pixel width for the elements but then allowing the contents to - // overflow. This means that the label and facepile will be shown, but will not - // push the button container off the edge of the panel. - .w_px() .child(Label::new(channel.name.clone())) .children(face_pile.map(|face_pile| face_pile.render(cx))), - ) - .end_slot::

( - // If we have a notification for either button, we want to show the corresponding - // button(s) as indicators. - if has_messages_notification || has_notes_notification { - Some( - button_container(cx).child( - h_stack() - .px_1() - .children( - // We only want to render the messages button if there are unseen messages. - // This way we don't take up any space that might overlap the channel name - // when there are no notifications. - has_messages_notification.then(|| messages_button(cx)), - ) - .child( - // We always want the notes button to take up space to prevent layout - // shift when hovering over the channel. - // However, if there are is no notes notification we just show an empty slot. - notes_button(cx) - .when(!has_notes_notification, |this| { - this.visible_on_hover("") - }), - ), - ), + ), + ) + .child( + h_stack() + .absolute() + .right(rems(0.)) + .h_full() + // HACK: Without this the channel name clips on top of the icons, but I'm not sure why. + .z_index(10) + .child( + h_stack() + .h_full() + .gap_1() + .px_1() + .child( + IconButton::new("channel_chat", IconName::MessageBubbles) + .style(ButtonStyle::Filled) + .size(ButtonSize::Compact) + .icon_size(IconSize::Small) + .icon_color(if has_messages_notification { + Color::Default + } else { + Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.join_channel_chat(channel_id, cx) + })) + .tooltip(|cx| Tooltip::text("Open channel chat", cx)) + .when(!has_messages_notification, |this| { + this.visible_on_hover("") + }), ) - } else { - None - }, - ) - .end_hover_slot( - // When we hover the channel entry we want to always show both buttons. - button_container(cx).child( - h_stack() - .px_1() - // The element hover background has a slight transparency to it, so we - // need to apply it to the inner element so that it blends with the solid - // background color of the absolutely-positioned element. - .group_hover("", |style| { - style.bg(cx.theme().colors().ghost_element_hover) - }) - .child(messages_button(cx)) - .child(notes_button(cx)), - ), + .child( + IconButton::new("channel_notes", IconName::File) + .style(ButtonStyle::Filled) + .size(ButtonSize::Compact) + .icon_size(IconSize::Small) + .icon_color(if has_notes_notification { + Color::Default + } else { + Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.open_channel_notes(channel_id, cx) + })) + .tooltip(|cx| Tooltip::text("Open channel notes", cx)) + .when(!has_notes_notification, |this| { + this.visible_on_hover("") + }), + ), ), ) .tooltip(|cx| Tooltip::text("Join channel", cx)) From 9ce7ef89497985c98341b6a452e4e8b9d2fc801e Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 16:14:54 -0800 Subject: [PATCH 062/334] Remove the last of the major todos --- crates/gpui/src/app/test_context.rs | 5 + crates/gpui/src/platform/test/platform.rs | 4 + crates/gpui/src/window.rs | 2 - crates/live_kit_client/examples/test_app.rs | 19 ++- crates/project_symbols/src/project_symbols.rs | 1 - crates/terminal_view/src/terminal_element.rs | 17 --- crates/terminal_view/src/terminal_view.rs | 6 +- crates/theme/src/styles/colors.rs | 1 + crates/theme/src/styles/players.rs | 2 - crates/vim/src/test/vim_test_context.rs | 8 +- crates/workspace/src/dock.rs | 26 ---- crates/workspace/src/notifications.rs | 2 - crates/workspace/src/pane.rs | 94 -------------- crates/workspace/src/searchable.rs | 13 +- crates/workspace/src/workspace.rs | 23 ---- crates/zed/src/zed.rs | 115 ++++++------------ script/bundle | 2 +- 17 files changed, 73 insertions(+), 267 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index ddf88537579809f9c5c2d22995d8b1379812c789..7db4b32002c03e8a28e667b710b6ad76f78598b2 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -111,6 +111,11 @@ impl TestAppContext { self.fn_name } + /// Checks whether there have been any new path prompts received by the platform. + pub fn did_prompt_for_new_path(&self) -> bool { + self.test_platform.did_prompt_for_new_path() + } + /// returns a new `TestAppContext` re-using the same executors to interleave tasks. pub fn new_app(&self) -> TestAppContext { Self::new(self.dispatcher.clone(), self.fn_name) diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index c4452a593a3747fd10534b75c7f0622479585075..38f23b94f20d43cc9b491f4269558240577fea4d 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -102,6 +102,10 @@ impl TestPlatform { }) .detach(); } + + pub(crate) fn did_prompt_for_new_path(&self) -> bool { + self.prompts.borrow().new_path.len() > 0 + } } impl Platform for TestPlatform { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ec4713639e930cd759a8af5cb4435ea588c5ec30..5b09b6ebd8ad78b5a78d6596aa84bb485d07ae97 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1474,9 +1474,7 @@ impl<'a> WindowContext<'a> { InputEvent::MouseUp(mouse_up) } InputEvent::MouseExited(mouse_exited) => { - // todo!("Should we record that the mouse is outside of the window somehow? Or are these global pixels?") self.window.modifiers = mouse_exited.modifiers; - InputEvent::MouseExited(mouse_exited) } InputEvent::ModifiersChanged(modifiers_changed) => { diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index 96407497aec4b2c94925a7a86dc914e99043169a..68a8a84209b1df63f81f972918a7f1dc63e77a74 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -1,7 +1,7 @@ use std::{sync::Arc, time::Duration}; use futures::StreamExt; -use gpui::{actions, KeyBinding}; +use gpui::{actions, KeyBinding, Menu, MenuItem}; use live_kit_client::{ LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, }; @@ -26,15 +26,14 @@ fn main() { cx.on_action(quit); cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]); - // todo!() - // cx.set_menus(vec![Menu { - // name: "Zed", - // items: vec![MenuItem::Action { - // name: "Quit", - // action: Box::new(Quit), - // os_action: None, - // }], - // }]); + cx.set_menus(vec![Menu { + name: "Zed", + items: vec![MenuItem::Action { + name: "Quit", + action: Box::new(Quit), + os_action: None, + }], + }]); let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index ed31ebd94997bd01d53b56df4f62dfff0518f737..23dfd21b8256574bf122215cd6d34e36d8059f1f 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -242,7 +242,6 @@ impl PickerDelegate for ProjectSymbolsDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - // todo!() combine_syntax_and_fuzzy_match_highlights() v_stack() .child( LabelLike::new().child( diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index bcaf147af239f9e36111a0a50980561ac4befe33..c52dbcb3d8e453729bd0d1ab57dc8da94e131f58 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -424,7 +424,6 @@ impl TerminalElement { let line_height = font_pixels * line_height.to_pixels(rem_size); let font_id = cx.text_system().resolve_font(&text_style.font()); - // todo!(do we need to keep this unwrap?) let cell_width = text_system .advance(font_id, font_pixels, 'm') .unwrap() @@ -524,7 +523,6 @@ impl TerminalElement { underline: Default::default(), }], ) - //todo!(do we need to keep this unwrap?) .unwrap() }; @@ -664,21 +662,6 @@ impl TerminalElement { }, ), ); - self.interactivity.on_click({ - let terminal = terminal.clone(); - move |e, cx| { - if e.down.button == MouseButton::Right { - let mouse_mode = terminal.update(cx, |terminal, _cx| { - terminal.mouse_mode(e.down.modifiers.shift) - }); - - if !mouse_mode { - //todo!(context menu) - // view.deploy_context_menu(e.position, cx); - } - } - } - }); self.interactivity.on_scroll_wheel({ let terminal = terminal.clone(); move |e, cx| { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 4d2e78f0daddacb0bc33673b8a14c29f54d3fdb4..b4a273dd0bc9085c4a6b3b069589ef1d9d640c5d 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -651,8 +651,10 @@ impl Render for TerminalView { .on_mouse_down( MouseButton::Right, cx.listener(|this, event: &MouseDownEvent, cx| { - this.deploy_context_menu(event.position, cx); - cx.notify(); + if !this.terminal.read(cx).mouse_mode(event.modifiers.shift) { + this.deploy_context_menu(event.position, cx); + cx.notify(); + } }), ) .child( diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index eb68b6c219f2090a57af9db2c9d1f67ff6dfacb9..b4d20c593067bd420974234012ee6b18908eec9f 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -222,6 +222,7 @@ pub struct ThemeStyles { #[refineable] pub status: StatusColors, + pub player: PlayerColors, pub syntax: Arc, } diff --git a/crates/theme/src/styles/players.rs b/crates/theme/src/styles/players.rs index d4d27e71237b0968d4642a92399dca754dada77e..b2b797db08b270513b77809437184f9b782af77d 100644 --- a/crates/theme/src/styles/players.rs +++ b/crates/theme/src/styles/players.rs @@ -122,12 +122,10 @@ impl PlayerColors { impl PlayerColors { pub fn local(&self) -> PlayerColor { - // todo!("use a valid color"); *self.0.first().unwrap() } pub fn absent(&self) -> PlayerColor { - // todo!("use a valid color"); *self.0.last().unwrap() } diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index 5ed5296bff44d3e76c32f2a4b768afd760d1d121..cf6d35044b2abb216a0494f7816e81a7b9663262 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -6,7 +6,7 @@ use editor::test::{ use futures::Future; use gpui::{Context, View, VisualContext}; use lsp::request; -use search::BufferSearchBar; +use search::{project_search::ProjectSearchBar, BufferSearchBar}; use crate::{state::Operator, *}; @@ -59,9 +59,9 @@ impl VimTestContext { pane.toolbar().update(cx, |toolbar, cx| { let buffer_search_bar = cx.new_view(BufferSearchBar::new); toolbar.add_item(buffer_search_bar, cx); - // todo!(); - // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new()); - // toolbar.add_item(project_search_bar, cx); + + let project_search_bar = cx.new_view(|_| ProjectSearchBar::new()); + toolbar.add_item(project_search_bar, cx); }) }); workspace.status_bar().update(cx, |status_bar, cx| { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index ed03695c5f2c234f3e7a633cb341da6002d344cd..0c752597262ca1f64a996d09795db289941a30b2 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -167,15 +167,6 @@ impl DockPosition { } } - // todo!() - // fn to_resize_handle_side(self) -> HandleSide { - // match self { - // Self::Left => HandleSide::Right, - // Self::Bottom => HandleSide::Top, - // Self::Right => HandleSide::Left, - // } - // } - pub fn axis(&self) -> Axis { match self { Self::Left | Self::Right => Axis::Horizontal, @@ -186,8 +177,6 @@ impl DockPosition { struct PanelEntry { panel: Arc, - // todo!() - // context_menu: View, _subscriptions: [Subscription; 2], } @@ -265,12 +254,6 @@ impl Dock { self.is_open } - // todo!() - // pub fn has_focus(&self, cx: &WindowContext) -> bool { - // self.visible_panel() - // .map_or(false, |panel| panel.has_focus(cx)) - // } - pub fn panel(&self) -> Option> { self.panel_entries .iter() @@ -417,16 +400,8 @@ impl Dock { }), ]; - // todo!() - // let dock_view_id = cx.view_id(); self.panel_entries.push(PanelEntry { panel: Arc::new(panel), - // todo!() - // context_menu: cx.add_view(|cx| { - // let mut menu = ContextMenu::new(dock_view_id, cx); - // menu.set_position_mode(OverlayPositionMode::Local); - // menu - // }), _subscriptions: subscriptions, }); cx.notify() @@ -618,7 +593,6 @@ impl PanelButtons { impl Render for PanelButtons { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - // todo!() let dock = self.dock.read(cx); let active_index = dock.active_panel_index; let is_open = dock.is_open; diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 36628290bb83ed6f640206f20f77933a577f58e3..6d46626d3631481a900f23286c8eb7dd219b42bd 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -8,8 +8,6 @@ use std::{any::TypeId, ops::DerefMut}; pub fn init(cx: &mut AppContext) { cx.set_global(NotificationTracker::new()); - // todo!() - // simple_message_notification::init(cx); } pub trait Notification: EventEmitter + Render {} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2a434b32d928bcd7d59de0bca03dad5c8d79d031..dad7b50ca6c307aef780910ee03249633ccfbfda 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -242,87 +242,6 @@ pub struct DraggedTab { pub is_active: bool, } -// pub struct DraggedItem { -// pub handle: Box, -// pub pane: WeakView, -// } - -// pub enum ReorderBehavior { -// None, -// MoveAfterActive, -// MoveToIndex(usize), -// } - -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// enum TabBarContextMenuKind { -// New, -// Split, -// } - -// struct TabBarContextMenu { -// kind: TabBarContextMenuKind, -// handle: View, -// } - -// impl TabBarContextMenu { -// fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option> { -// if self.kind == kind { -// return Some(self.handle.clone()); -// } -// None -// } -// } - -// #[allow(clippy::too_many_arguments)] -// fn nav_button)>( -// svg_path: &'static str, -// style: theme::Interactive, -// nav_button_height: f32, -// tooltip_style: TooltipStyle, -// enabled: bool, -// on_click: F, -// tooltip_action: A, -// action_name: &str, -// cx: &mut ViewContext, -// ) -> AnyElement { -// MouseEventHandler::new::(0, cx, |state, _| { -// let style = if enabled { -// style.style_for(state) -// } else { -// style.disabled_style() -// }; -// Svg::new(svg_path) -// .with_color(style.color) -// .constrained() -// .with_width(style.icon_width) -// .aligned() -// .contained() -// .with_style(style.container) -// .constrained() -// .with_width(style.button_width) -// .with_height(nav_button_height) -// .aligned() -// .top() -// }) -// .with_cursor_style(if enabled { -// CursorStyle::PointingHand -// } else { -// CursorStyle::default() -// }) -// .on_click(MouseButton::Left, move |_, toolbar, cx| { -// on_click(toolbar, cx) -// }) -// .with_tooltip::( -// 0, -// action_name.to_string(), -// Some(Box::new(tooltip_action)), -// tooltip_style, -// cx, -// ) -// .contained() -// .into_any_named("nav button") -// } - impl EventEmitter for Pane {} impl Pane { @@ -333,13 +252,6 @@ impl Pane { can_drop_predicate: Option bool + 'static>>, cx: &mut ViewContext, ) -> Self { - // todo!("context menu") - // let pane_view_id = cx.view_id(); - // let context_menu = cx.build_view(|cx| ContextMenu::new(pane_view_id, cx)); - // context_menu.update(cx, |menu, _| { - // menu.set_position_mode(OverlayPositionMode::Local) - // }); - // let focus_handle = cx.focus_handle(); let subscriptions = vec![ @@ -370,11 +282,6 @@ impl Pane { split_item_menu: None, tab_bar_scroll_handle: ScrollHandle::new(), drag_split_direction: None, - // tab_bar_context_menu: TabBarContextMenu { - // kind: TabBarContextMenuKind::New, - // handle: context_menu, - // }, - // tab_context_menu: cx.build_view(|_| ContextMenu::new(pane_view_id, cx)), workspace, project, can_drop_predicate, @@ -450,7 +357,6 @@ impl Pane { } pub fn has_focus(&self, cx: &WindowContext) -> bool { - // todo!(); // inline this manually self.focus_handle.contains_focused(cx) } diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index 59202cbbaf53e5e4f62d77be693e3b13916b555b..e1f93f31cb22cc7fe7c7b23158de6bb034424526 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -1,8 +1,8 @@ use std::{any::Any, sync::Arc}; use gpui::{ - AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView, - WindowContext, + AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, + WeakView, WindowContext, }; use project::search::SearchQuery; @@ -127,7 +127,6 @@ pub trait SearchableItemHandle: ItemHandle { ) -> Option; } -// todo!("here is where we need to use AnyWeakView"); impl SearchableItemHandle for View { fn downgrade(&self) -> Box { Box::new(self.downgrade()) @@ -249,7 +248,7 @@ impl Eq for Box {} pub trait WeakSearchableItemHandle: WeakItemHandle { fn upgrade(&self, cx: &AppContext) -> Option>; - // fn into_any(self) -> AnyWeakView; + fn into_any(self) -> AnyWeakView; } impl WeakSearchableItemHandle for WeakView { @@ -257,9 +256,9 @@ impl WeakSearchableItemHandle for WeakView { Some(Box::new(self.upgrade()?)) } - // fn into_any(self) -> AnyView { - // self.into_any() - // } + fn into_any(self) -> AnyWeakView { + self.into() + } } impl PartialEq for Box { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 739ce88636ca32a88be1496103ad03ed42f63808..2bff02b4f6b14ead673d359e7c579e9f00f007e5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1143,7 +1143,6 @@ impl Workspace { quitting: bool, cx: &mut ViewContext, ) -> Task> { - //todo!(saveing) let active_call = self.active_call().cloned(); let window = cx.window_handle(); @@ -1694,28 +1693,6 @@ impl Workspace { None } - // todo!("implement zoom") - #[allow(unused)] - fn zoom_out(&mut self, cx: &mut ViewContext) { - for pane in &self.panes { - pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); - } - - self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); - self.zoomed = None; - self.zoomed_position = None; - - cx.notify(); - } - - // todo!() - // #[cfg(any(test, feature = "test-support"))] - // pub fn zoomed_view(&self, cx: &AppContext) -> Option { - // self.zoomed.and_then(|view| view.upgrade(cx)) - // } - fn dismiss_zoomed_items_to_reveal( &mut self, dock_to_reveal: Option, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0b90961d2352a31e80f38dbd3eda86df4055e788..38e0bec14e30418bee589450bc21cccc31364daf 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -23,7 +23,7 @@ use quick_action_bar::QuickActionBar; use search::project_search::ProjectSearchBar; use settings::{initial_local_settings_content, KeymapFile, Settings, SettingsStore}; use std::{borrow::Cow, ops::Deref, sync::Arc}; -use terminal_view::terminal_panel::TerminalPanel; +use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{ asset_str, channel::{AppCommitSha, ReleaseChannel}, @@ -299,79 +299,42 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { ); }, ) - //todo!() - // cx.add_action({ - // move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { - // let app_state = workspace.app_state().clone(); - // let markdown = app_state.languages.language_for_name("JSON"); - // let window = cx.window(); - // cx.spawn(|workspace, mut cx| async move { - // let markdown = markdown.await.log_err(); - // let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| { - // anyhow!("could not debug elements for window {}", window.id()) - // })?) - // .unwrap(); - // workspace - // .update(&mut cx, |workspace, cx| { - // workspace.with_local_workspace(cx, move |workspace, cx| { - // let project = workspace.project().clone(); - // let buffer = project - // .update(cx, |project, cx| { - // project.create_buffer(&content, markdown, cx) - // }) - // .expect("creating buffers on a local workspace always succeeds"); - // let buffer = cx.add_model(|cx| { - // MultiBuffer::singleton(buffer, cx) - // .with_title("Debug Elements".into()) - // }); - // workspace.add_item( - // Box::new(cx.add_view(|cx| { - // Editor::for_multibuffer(buffer, Some(project.clone()), cx) - // })), - // cx, - // ); - // }) - // })? - // .await - // }) - // .detach_and_log_err(cx); - // } - // }); - // .register_action( - // |workspace: &mut Workspace, - // _: &project_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::collab_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::chat_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::notification_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &terminal_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); + .register_action( + |workspace: &mut Workspace, + _: &project_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::collab_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::chat_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &collab_ui::notification_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace + .toggle_panel_focus::(cx); + }, + ) + .register_action( + |workspace: &mut Workspace, + _: &terminal_panel::ToggleFocus, + cx: &mut ViewContext| { + workspace.toggle_panel_focus::(cx); + }, + ) .register_action({ let app_state = Arc::downgrade(&app_state); move |_, _: &NewWindow, cx| { @@ -1658,8 +1621,8 @@ mod tests { }) .unwrap(); save_task.await.unwrap(); - // todo!() po - //assert!(!cx.did_prompt_for_new_path()); + + assert!(!cx.did_prompt_for_new_path()); window .update(cx, |_, cx| { editor.update(cx, |editor, cx| { diff --git a/script/bundle b/script/bundle index 462706679905cbd1b4250f2b455120dfe3e9fcf1..9c0dddbac49bf71dc9ca55534284afadacef9b52 100755 --- a/script/bundle +++ b/script/bundle @@ -132,7 +132,7 @@ else cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" fi -#todo!(The app identifier has been set to 'Dev', but the channel is nightly, RATIONALIZE ALL OF THIS MESS) +# Note: The app identifier for our development builds is the same as the app identifier for nightly. cp crates/${zed_crate}/contents/$channel/embedded.provisionprofile "${app_path}/Contents/" if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then From e786e221212bcce55c47cd6d7e553ded137e2daa Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 16:31:12 -0800 Subject: [PATCH 063/334] And a few more todos --- crates/collab/src/tests/editor_tests.rs | 11 +++++++---- crates/gpui/src/platform/test/platform.rs | 3 +-- crates/vcs_menu/src/lib.rs | 1 - crates/workspace/src/pane_group.rs | 11 +---------- crates/workspace/src/workspace.rs | 6 +++++- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 6f06e9f10faadf77d44eecbf43f6026c2799adca..0c3601b07531bf5c77459fd5530a31ba8ef68717 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -71,6 +71,7 @@ async fn test_host_disconnect( let workspace_b = cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx)); let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b); + let workspace_b_view = workspace_b.root_view(cx_b).unwrap(); let editor_b = workspace_b .update(cx_b, |workspace, cx| { @@ -85,8 +86,10 @@ async fn test_host_disconnect( //TODO: focus assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx))); editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); - //todo(is_edited) - // assert!(workspace_b.is_edited(cx_b)); + + cx_b.update(|cx| { + assert!(workspace_b_view.read(cx).is_edited()); + }); // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared. server.forbid_connections(); @@ -105,11 +108,11 @@ async fn test_host_disconnect( // Ensure client B's edited state is reset and that the whole window is blurred. workspace_b - .update(cx_b, |_, cx| { + .update(cx_b, |workspace, cx| { assert_eq!(cx.focused(), None); + assert!(!workspace.is_edited()) }) .unwrap(); - // assert!(!workspace_b.is_edited(cx_b)); // Ensure client B is not prompted to save edits when closing window after disconnecting. let can_close = workspace_b diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 38f23b94f20d43cc9b491f4269558240577fea4d..a7dc6d48419cc2e5d54dc132617752ae030e561e 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -282,8 +282,7 @@ impl Platform for TestPlatform { } fn should_auto_hide_scrollbars(&self) -> bool { - // todo() - true + false } fn write_to_clipboard(&self, item: ClipboardItem) { diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index 2735f81677c86331e554dd3f27b2aa2362587d5d..0774c6f5755324de77b16f691ee0dde11ed38f16 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -18,7 +18,6 @@ use workspace::{ModalView, Toast, Workspace}; actions!(branches, [OpenRecent]); pub fn init(cx: &mut AppContext) { - // todo!() po cx.observe_new_views(|workspace: &mut Workspace, _| { workspace.register_action(|workspace, action, cx| { BranchList::toggle_modal(workspace, action, cx).log_err(); diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index e28d0e63deda2a97ac3c4a9cd77d4beebb802333..3dcdeec37f4e2cceb9e2159d6577b303f5355979 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -12,7 +12,7 @@ use serde::Deserialize; use std::sync::Arc; use ui::{prelude::*, Button}; -const HANDLE_HITBOX_SIZE: f32 = 4.0; +pub const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; @@ -268,15 +268,6 @@ impl Member { ) }) .into_any() - - // let el = div() - // .flex() - // .flex_1() - // .gap_px() - // .w_full() - // .h_full() - // .bg(cx.theme().colors().editor) - // .children(); } Member::Axis(axis) => axis .render( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2bff02b4f6b14ead673d359e7c579e9f00f007e5..a1d6ab499ae11a77425b2430eb9a0c5420f5011e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -852,6 +852,10 @@ impl Workspace { &self.right_dock } + pub fn is_edited(&self) -> bool { + self.window_edited + } + pub fn add_panel(&mut self, panel: View, cx: &mut ViewContext) { let dock = match panel.position(cx) { DockPosition::Left => &self.left_dock, @@ -2055,7 +2059,7 @@ impl Workspace { _ => bounding_box.center(), }; - let distance_to_next = 8.; //todo(pane dividers styling) + let distance_to_next = pane_group::HANDLE_HITBOX_SIZE; let target = match direction { SplitDirection::Left => { From 1bf33b4b61acfda1cf4ee0c6f67e4059bc6e839a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 20:31:13 -0700 Subject: [PATCH 064/334] Ensure focus_in and focus_out fire on window activation Also: - Rename cx.on_blur to cx.on_focus_lost - Fix a bug where notify calls in focus handlers were ignored - Fix a bug where vim would get stuck in the wrong mode when switching windows --- crates/gpui/src/window.rs | 63 ++++++++++++++++++++----------- crates/vim/src/editor_events.rs | 40 +++++++++++++++++++- crates/workspace/src/workspace.rs | 2 +- 3 files changed, 80 insertions(+), 25 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ec4713639e930cd759a8af5cb4435ea588c5ec30..bd0e72054982d4d3760d0d06b32b17fe58c0c38f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -269,7 +269,7 @@ pub struct Window { frame_arena: Arena, pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, - blur_listeners: SubscriberSet<(), AnyObserver>, + focus_lost_listeners: SubscriberSet<(), AnyObserver>, default_prevented: bool, mouse_position: Point, modifiers: Modifiers, @@ -296,6 +296,7 @@ pub(crate) struct ElementStateBox { pub(crate) struct Frame { focus: Option, + window_active: bool, pub(crate) element_states: FxHashMap, mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, @@ -311,6 +312,7 @@ impl Frame { fn new(dispatch_tree: DispatchTree) -> Self { Frame { focus: None, + window_active: false, element_states: FxHashMap::default(), mouse_listeners: FxHashMap::default(), dispatch_tree, @@ -417,7 +419,7 @@ impl Window { frame_arena: Arena::new(1024 * 1024), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), - blur_listeners: SubscriberSet::new(), + focus_lost_listeners: SubscriberSet::new(), default_prevented: true, mouse_position, modifiers, @@ -1406,29 +1408,14 @@ impl<'a> WindowContext<'a> { self.window.focus, ); self.window.next_frame.focus = self.window.focus; + self.window.next_frame.window_active = self.window.active; self.window.root_view = Some(root_view); let previous_focus_path = self.window.rendered_frame.focus_path(); + let previous_window_active = self.window.rendered_frame.window_active; mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); let current_focus_path = self.window.rendered_frame.focus_path(); - - if previous_focus_path != current_focus_path { - if !previous_focus_path.is_empty() && current_focus_path.is_empty() { - self.window - .blur_listeners - .clone() - .retain(&(), |listener| listener(self)); - } - - let event = FocusEvent { - previous_focus_path, - current_focus_path, - }; - self.window - .focus_listeners - .clone() - .retain(&(), |listener| listener(&event, self)); - } + let current_window_active = self.window.rendered_frame.window_active; let scene = self.window.rendered_frame.scene_builder.build(); @@ -1445,6 +1432,34 @@ impl<'a> WindowContext<'a> { self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); + if previous_focus_path != current_focus_path + || previous_window_active != current_window_active + { + if !previous_focus_path.is_empty() && current_focus_path.is_empty() { + self.window + .focus_lost_listeners + .clone() + .retain(&(), |listener| listener(self)); + } + + let event = FocusEvent { + previous_focus_path: if previous_window_active { + previous_focus_path + } else { + Default::default() + }, + current_focus_path: if current_window_active { + current_focus_path + } else { + Default::default() + }, + }; + self.window + .focus_listeners + .clone() + .retain(&(), |listener| listener(&event, self)); + } + scene } @@ -2645,14 +2660,16 @@ impl<'a, V: 'static> ViewContext<'a, V> { subscription } - /// Register a listener to be called when the window loses focus. + /// Register a listener to be called when nothing in the window has focus. + /// This typically happens when the node that was focused is removed from the tree, + /// and this callback lets you chose a default place to restore the users focus. /// Returns a subscription and persists until the subscription is dropped. - pub fn on_blur_window( + pub fn on_focus_lost( &mut self, mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, ) -> Subscription { let view = self.view.downgrade(); - let (subscription, activate) = self.window.blur_listeners.insert( + let (subscription, activate) = self.window.focus_lost_listeners.insert( (), Box::new(move |cx| view.update(cx, |view, cx| listener(view, cx)).is_ok()), ); diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index 3c2f373f9455209a36ef9dac88c3144f7f1a4bdd..e3c759aaebdcc7de27d593036a7046e4784269b1 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -69,7 +69,7 @@ fn released(entity_id: EntityId, cx: &mut AppContext) { mod test { use crate::{test::VimTestContext, Vim}; use editor::Editor; - use gpui::{Context, Entity}; + use gpui::{Context, Entity, VisualTestContext}; use language::Buffer; // regression test for blur called with a different active editor @@ -101,4 +101,42 @@ mod test { editor1.handle_blur(cx); }); } + + // regression test for focus_in/focus_out being called on window activation + #[gpui::test] + async fn test_focus_across_windows(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + let mut cx1 = VisualTestContext::from_window(cx.window, &cx); + let editor1 = cx.editor.clone(); + dbg!(editor1.entity_id()); + + let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); + let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); + + editor2.update(cx2, |_, cx| { + cx.focus_self(); + cx.activate_window(); + }); + cx.run_until_parked(); + + cx1.update(|cx| { + assert_eq!( + Vim::read(cx).active_editor.as_ref().unwrap().entity_id(), + editor2.entity_id(), + ) + }); + + cx1.update(|cx| { + cx.activate_window(); + }); + cx.run_until_parked(); + + cx.update(|cx| { + assert_eq!( + Vim::read(cx).active_editor.as_ref().unwrap().entity_id(), + editor1.entity_id(), + ) + }); + } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 09e0a1378d440e0bbf2eb2a7dc21ec88557fa320..ad74f44f17bb9775b74cde8ecbc32fc377f8503d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -537,7 +537,7 @@ impl Workspace { }) .detach(); - cx.on_blur_window(|this, cx| { + cx.on_focus_lost(|this, cx| { let focus_handle = this.focus_handle(cx); cx.focus(&focus_handle); }) From 72c022f4135e26359fb215feb1ab17b552bbd140 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 21:37:25 -0700 Subject: [PATCH 065/334] Ensure focus-sensitive tests have active windows --- crates/collab/src/tests/following_tests.rs | 4 ++++ crates/collab/src/tests/test_server.rs | 5 ++++- crates/gpui/src/app/test_context.rs | 1 + crates/vim/src/editor_events.rs | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 0486e294619fd4fd34604c6cc4113fb03f1057ad..d50b34859d13c19d9cac7b7796ba3b840b2da3a9 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -76,6 +76,10 @@ async fn test_basic_following( let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + cx_b.update(|cx| { + assert!(cx.is_window_active()); + }); + // Client A opens some editors. let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone()); let editor_a1 = workspace_a diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 034a85961f8e98966a2764c4196682fabf4b33cf..4f3634c7b1f9c9575a177810d9e5eb89ed12c891 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -617,7 +617,10 @@ impl TestClient { project: &Model, cx: &'a mut TestAppContext, ) -> (View, &'a mut VisualTestContext) { - cx.add_window_view(|cx| Workspace::new(0, project.clone(), self.app_state.clone(), cx)) + cx.add_window_view(|cx| { + cx.activate_window(); + Workspace::new(0, project.clone(), self.app_state.clone(), cx) + }) } } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index de31339b8d79bb3b4a80d4e8c9bc95834ffef92c..7ff751ef1ce7232e7714eac6bf2ddc87892a9e39 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -174,6 +174,7 @@ impl TestAppContext { drop(cx); let view = window.root_view(self).unwrap(); let cx = Box::new(VisualTestContext::from_window(*window.deref(), self)); + cx.run_until_parked(); // it might be nice to try and cleanup these at the end of each test. (view, Box::leak(cx)) } diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index e3c759aaebdcc7de27d593036a7046e4784269b1..e3ed076698d101a12f92c16048748532ea596e1c 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -82,11 +82,13 @@ mod test { let editor2 = cx .update(|cx| { window2.update(cx, |_, cx| { + cx.activate_window(); cx.focus_self(); cx.view().clone() }) }) .unwrap(); + cx.run_until_parked(); cx.update(|cx| { let vim = Vim::read(cx); From 2ca462722cea521dc66935e88f87f1b3fffc78ca Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 9 Jan 2024 22:11:09 -0700 Subject: [PATCH 066/334] Fix some tests (mostly more run_until_parked's...) --- crates/collab/src/tests/channel_tests.rs | 1 + crates/collab/src/tests/following_tests.rs | 15 +++++---------- crates/collab/src/tests/integration_tests.rs | 1 + crates/gpui/src/app/test_context.rs | 3 ++- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 49e7060301a4279adda4439aa39784b066ed76dd..2a88bc4c579db9855184e278d1fef37200d1f470 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1337,6 +1337,7 @@ async fn test_guest_access( }) .await .unwrap(); + executor.run_until_parked(); assert_channels_list_shape(client_b.channel_store(), cx_b, &[]); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 0486e294619fd4fd34604c6cc4113fb03f1057ad..46524319fbb4aab97d570f801c8b187867a52af4 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -234,14 +234,14 @@ async fn test_basic_following( workspace_c.update(cx_c, |workspace, cx| { workspace.close_window(&Default::default(), cx); }); - cx_c.update(|_| { - drop(workspace_c); - }); - cx_b.executor().run_until_parked(); + executor.run_until_parked(); // are you sure you want to leave the call? cx_c.simulate_prompt_answer(0); - cx_b.executor().run_until_parked(); + cx_c.cx.update(|_| { + drop(workspace_c); + }); executor.run_until_parked(); + cx_c.cx.update(|_| {}); weak_workspace_c.assert_dropped(); weak_project_c.assert_dropped(); @@ -1363,8 +1363,6 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; - cx_a.update(editor::init); - cx_b.update(editor::init); client_a .fs() @@ -1400,9 +1398,6 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx)); - cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx)); - active_call_a .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index a21235b6f3914a8cc6e181c9ae128da011f8bce3..cedc841527ff5c4c40d2630dc9c15166ccec46d8 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -3065,6 +3065,7 @@ async fn test_local_settings( .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) .await .unwrap(); + executor.run_until_parked(); // As client B, join that project and observe the local settings. let project_b = client_b.build_remote_project(project_id, cx_b).await; diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 2b6c361a782bc7f4852aa5755fbfdd31f2b6a36f..d20743483581f1b0cfe771889dd36b855b7d2ed2 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -545,7 +545,8 @@ use derive_more::{Deref, DerefMut}; pub struct VisualTestContext { #[deref] #[deref_mut] - cx: TestAppContext, + /// cx is the original TestAppContext (you can more easily access this using Deref) + pub cx: TestAppContext, window: AnyWindowHandle, } From d3d6b53a74befa93cfaa5b44f2e82dc7aea0d90c Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 8 Jan 2024 16:32:59 -0500 Subject: [PATCH 067/334] WIP --- crates/client/src/telemetry.rs | 43 +++- .../client/src/telemetry/event_coalescer.rs | 224 ++++++++++++++++++ crates/editor/src/editor.rs | 4 + 3 files changed, 265 insertions(+), 6 deletions(-) create mode 100644 crates/client/src/telemetry/event_coalescer.rs diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 26b5748187ff735ebedf2cdb38753bdb6d9fcedc..6db434cffb625dafd379d95dbdcd7b540889b38b 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,3 +1,9 @@ +// TODO - Test if locking slows Zed typing down +// TODO - Make sure to send last event on flush +// TODO - Move code to be used as arcs in editor and terminal + +mod event_coalescer; + use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use chrono::{DateTime, Utc}; use futures::Future; @@ -5,7 +11,6 @@ use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task}; use lazy_static::lazy_static; use parking_lot::Mutex; use serde::Serialize; -use serde_json; use settings::{Settings, SettingsStore}; use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration}; use sysinfo::{ @@ -15,6 +20,8 @@ use tempfile::NamedTempFile; use util::http::HttpClient; use util::{channel::ReleaseChannel, TryFutureExt}; +use self::event_coalescer::EventCoalescer; + pub struct Telemetry { http_client: Arc, executor: BackgroundExecutor, @@ -34,6 +41,7 @@ struct TelemetryState { log_file: Option, is_staff: Option, first_event_datetime: Option>, + edit_activity: EventCoalescer, } const EVENTS_URL_PATH: &'static str = "/api/events"; @@ -118,6 +126,11 @@ pub enum Event { value: String, milliseconds_since_first_event: i64, }, + Edit { + duration: i64, + environment: &'static str, + milliseconds_since_first_event: i64, + }, } #[cfg(debug_assertions)] @@ -127,10 +140,10 @@ const MAX_QUEUE_LEN: usize = 1; const MAX_QUEUE_LEN: usize = 50; #[cfg(debug_assertions)] -const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); +const FLUSH_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); #[cfg(not(debug_assertions))] -const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5); +const FLUSH_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5); impl Telemetry { pub fn new(client: Arc, cx: &mut AppContext) -> Arc { @@ -150,11 +163,12 @@ impl Telemetry { installation_id: None, metrics_id: None, session_id: None, - events_queue: Default::default(), - flush_events_task: Default::default(), + events_queue: Vec::new(), + flush_events_task: None, log_file: None, is_staff: None, first_event_datetime: None, + edit_activity: EventCoalescer::new(), })); cx.observe_global::({ @@ -392,6 +406,23 @@ impl Telemetry { } } + pub fn log_edit_event(self: &Arc, environment: &'static str) { + let mut state = self.state.lock(); + + let coalesced_duration = state.edit_activity.log_event(environment); + + if let Some((start, end)) = coalesced_duration { + let event = Event::Edit { + duration: end.timestamp_millis() - start.timestamp_millis(), + environment, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + drop(state); + self.report_event(event); + } + } + fn report_event(self: &Arc, event: Event) { let mut state = self.state.lock(); @@ -410,7 +441,7 @@ impl Telemetry { let this = self.clone(); let executor = self.executor.clone(); state.flush_events_task = Some(self.executor.spawn(async move { - executor.timer(DEBOUNCE_INTERVAL).await; + executor.timer(FLUSH_DEBOUNCE_INTERVAL).await; this.flush_events(); })); } diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs new file mode 100644 index 0000000000000000000000000000000000000000..6369ebccbc8b8530d1d4c8aa0416545cb6fff02c --- /dev/null +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -0,0 +1,224 @@ +use chrono::{DateTime, Duration, Utc}; +use std::time; + +const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20); +const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from_millis(1); + +pub struct EventCoalescer { + environment: Option<&'static str>, + period_start: Option>, + period_end: Option>, +} + +impl EventCoalescer { + pub fn new() -> Self { + Self { + environment: None, + period_start: None, + period_end: None, + } + } + + pub fn log_event( + &mut self, + environment: &'static str, + ) -> Option<(DateTime, DateTime)> { + self.log_event_with_time(Utc::now(), environment) + } + + fn log_event_with_time( + &mut self, + log_time: DateTime, + environment: &'static str, + ) -> Option<(DateTime, DateTime)> { + let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap(); + + let Some(period_start) = self.period_start else { + self.period_start = Some(log_time); + self.environment = Some(environment); + return None; + }; + + let period_end = self + .period_end + .unwrap_or(period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT); + let within_timeout = log_time - period_end < coalesce_timeout; + let environment_is_same = self.environment == Some(environment); + let should_coaelesce = !within_timeout || !environment_is_same; + + if should_coaelesce { + self.period_start = Some(log_time); + self.period_end = None; + self.environment = Some(environment); + return Some(( + period_start, + if within_timeout { log_time } else { period_end }, + )); + } + + self.period_end = Some(log_time); + + None + } +} + +#[cfg(test)] +mod tests { + use chrono::TimeZone; + + use super::*; + + #[test] + fn test_same_context_exceeding_timeout() { + let environment_1 = "environment_1"; + let mut event_coalescer = EventCoalescer::new(); + + assert_eq!(event_coalescer.period_start, None); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, None); + + let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); + let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let mut period_end = period_start; + + // Ensure that many calls within the timeout don't start a new period + for _ in 0..100 { + period_end += within_timeout_adjustment; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, Some(period_end)); + assert_eq!(event_coalescer.environment, Some(environment_1)); + } + + let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); + // Logging an event exceeding the timeout should start a new period + let new_period_start = period_end + exceed_timeout_adjustment; + let coalesced_duration = + event_coalescer.log_event_with_time(new_period_start, environment_1); + + assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!(event_coalescer.period_start, Some(new_period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + } + + #[test] + fn test_different_environment_under_timeout() { + let environment_1 = "environment_1"; + let mut event_coalescer = EventCoalescer::new(); + + assert_eq!(event_coalescer.period_start, None); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, None); + + let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); + let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let period_end = period_start + within_timeout_adjustment; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, Some(period_end)); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + // Logging an event within the timeout but with a different environment should start a new period + let period_end = period_end + within_timeout_adjustment; + let environment_2 = "environment_2"; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + + assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!(event_coalescer.period_start, Some(period_end)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_2)); + } + + #[test] + fn test_switching_environment_while_within_timeout() { + let environment_1 = "environment_1"; + let mut event_coalescer = EventCoalescer::new(); + + assert_eq!(event_coalescer.period_start, None); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, None); + + let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); + let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let period_end = period_start + within_timeout_adjustment; + let environment_2 = "environment_2"; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + + assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!(event_coalescer.period_start, Some(period_end)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_2)); + } + // 0 20 40 60 + // |-------------------|-------------------|-------------------|------------------- + // |--------|----------env change + // |------------------- + // |period_start |period_end + // |new_period_start + + #[test] + fn test_switching_environment_while_exceeding_timeout() { + let environment_1 = "environment_1"; + let mut event_coalescer = EventCoalescer::new(); + + assert_eq!(event_coalescer.period_start, None); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, None); + + let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); + let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + + assert_eq!(coalesced_duration, None); + assert_eq!(event_coalescer.period_start, Some(period_start)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_1)); + + let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); + let period_end = period_start + exceed_timeout_adjustment; + let environment_2 = "environment_2"; + let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + + assert_eq!( + coalesced_duration, + Some(( + period_start, + period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT + )) + ); + assert_eq!(event_coalescer.period_start, Some(period_end)); + assert_eq!(event_coalescer.period_end, None); + assert_eq!(event_coalescer.environment, Some(environment_2)); + } + // 0 20 40 60 + // |-------------------|-------------------|-------------------|------------------- + // |--------|----------------------------------------env change + // |-------------------| + // |period_start |period_end + // |new_period_start +} diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 231f76218a44125e6c42f2a99f98027f98414ab1..0920de080eb404b6c778b0d4cfa57fcd4344ef3e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8664,6 +8664,10 @@ impl Editor { } } } + + let Some(project) = &self.project else { return }; + let telemetry = project.read(cx).client().telemetry().clone(); + telemetry.log_edit_event("editor"); } multi_buffer::Event::ExcerptsAdded { buffer, From 6809b92e34bb038f8e0486dcd7dd471384aafccb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 10:16:09 +0200 Subject: [PATCH 068/334] Disable synthetic drag on drag and drop Otherwise, conflicting MouseMove events are generated and page regions start to flicker. --- crates/gpui/src/platform/mac/window.rs | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 2beac528c18f53cfa9a39b008dbebf3825502b30..6d03a3b5cd698c62147bd75b62c3594309f38797 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -338,6 +338,7 @@ struct MacWindowState { ime_state: ImeState, // Retains the last IME Text ime_text: Option, + external_files_dragged: bool, } impl MacWindowState { @@ -567,6 +568,7 @@ impl MacWindow { previous_modifiers_changed_event: None, ime_state: ImeState::None, ime_text: None, + external_files_dragged: false, }))); (*native_window).set_ivar( @@ -1223,15 +1225,20 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { .. }, ) => { - lock.synthetic_drag_counter += 1; - let executor = lock.executor.clone(); - executor - .spawn(synthetic_drag( - weak_window_state, - lock.synthetic_drag_counter, - event.clone(), - )) - .detach(); + // Synthetic drag is used for selecting long buffer contents while buffer is being scrolled. + // External file drag and drop is able to emit its own synthetic mouse events which will conflict + // with these ones. + if !lock.external_files_dragged { + lock.synthetic_drag_counter += 1; + let executor = lock.executor.clone(); + executor + .spawn(synthetic_drag( + weak_window_state, + lock.synthetic_drag_counter, + event.clone(), + )) + .detach(); + } } InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, @@ -1675,6 +1682,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr let paths = external_paths_from_event(dragging_info); InputEvent::FileDrop(FileDropEvent::Entered { position, paths }) }) { + window_state.lock().external_files_dragged = true; NSDragOperationCopy } else { NSDragOperationNone @@ -1697,6 +1705,7 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited)); + window_state.lock().external_files_dragged = false; } extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL { From 927e0db750a55460c58b07947b2773cbd8284bc6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 Jan 2024 23:31:06 +0200 Subject: [PATCH 069/334] An attempt to defer scrolls during empty initial state --- crates/gpui/src/elements/uniform_list.rs | 32 +++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index ffa678e9e5ed4ef8281c97067bc411451dda507e..244e8cde088be5942ad4a5887733b8b9795adbd7 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -64,7 +64,10 @@ pub struct UniformList { } #[derive(Clone, Default)] -pub struct UniformListScrollHandle(Rc>>); +pub struct UniformListScrollHandle { + state: Rc>>, + deferred_scroll_to_item: Option, +} #[derive(Clone, Debug)] struct ScrollHandleState { @@ -75,11 +78,14 @@ struct ScrollHandleState { impl UniformListScrollHandle { pub fn new() -> Self { - Self(Rc::new(RefCell::new(None))) + Self { + state: Rc::new(RefCell::new(None)), + deferred_scroll_to_item: None, + } } - pub fn scroll_to_item(&self, ix: usize) { - if let Some(state) = &*self.0.borrow() { + pub fn scroll_to_item(&mut self, ix: usize) { + if let Some(state) = &*self.state.borrow() { let mut scroll_offset = state.scroll_offset.borrow_mut(); let item_top = state.item_height * ix; let item_bottom = item_top + state.item_height; @@ -89,13 +95,16 @@ impl UniformListScrollHandle { } else if item_bottom > scroll_top + state.list_height { scroll_offset.y = -(item_bottom - state.list_height); } + } else { + self.deferred_scroll_to_item = Some(ix); } } - pub fn scroll_top(&self) -> Pixels { - if let Some(state) = &*self.0.borrow() { + pub fn scroll_top(&mut self) -> Pixels { + if let Some(state) = &*self.state.borrow() { -state.scroll_offset.borrow().y } else { + self.deferred_scroll_to_item = Some(0); Pixels::ZERO } } @@ -192,7 +201,7 @@ impl Element for UniformList { .scroll_offset .get_or_insert_with(|| { if let Some(scroll_handle) = self.scroll_handle.as_ref() { - if let Some(scroll_handle) = scroll_handle.0.borrow().as_ref() { + if let Some(scroll_handle) = scroll_handle.state.borrow().as_ref() { return scroll_handle.scroll_offset.clone(); } } @@ -228,12 +237,17 @@ impl Element for UniformList { scroll_offset.y = min_scroll_offset; } - if let Some(scroll_handle) = self.scroll_handle.clone() { - scroll_handle.0.borrow_mut().replace(ScrollHandleState { + if let Some(scroll_handle) = self.scroll_handle.as_mut() { + scroll_handle.state.borrow_mut().replace(ScrollHandleState { item_height, list_height: padded_bounds.size.height, scroll_offset: shared_scroll_offset, }); + if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { + dbg!("@@@@"); + scroll_handle.scroll_to_item(ix); + cx.notify(); + } } let first_visible_element_ix = From 881c532256d4263edc55251e05b163f38de915f6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 Jan 2024 10:50:16 +0100 Subject: [PATCH 070/334] Insert primitives associated with views from a previous scene --- crates/gpui/src/app/entity_map.rs | 8 +- crates/gpui/src/key_dispatch.rs | 3 + crates/gpui/src/scene.rs | 218 ++++++++++++++++++------------ crates/gpui/src/view.rs | 2 +- crates/gpui/src/window.rs | 41 +++--- 5 files changed, 164 insertions(+), 108 deletions(-) diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 17f6e47ddfec85de1c82b1fc0b8cd6ffc285aa32..482a003fe76fdbab46b8447efff2610ffaec52e3 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -2,7 +2,7 @@ use crate::{seal::Sealed, AppContext, Context, Entity, ModelContext}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; -use slotmap::{SecondaryMap, SlotMap}; +use slotmap::{KeyData, SecondaryMap, SlotMap}; use std::{ any::{type_name, Any, TypeId}, fmt::{self, Display}, @@ -21,6 +21,12 @@ use collections::HashMap; slotmap::new_key_type! { pub struct EntityId; } +impl From for EntityId { + fn from(value: u64) -> Self { + Self(KeyData::from_ffi(value)) + } +} + impl EntityId { pub fn as_u64(self) -> u64 { self.0.as_ffi() diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index c6abc101eabd398ce3beafe7f7c0da85702174e2..168b1da9fe7e54d9a36982deaed8972dd8b5c482 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -144,6 +144,8 @@ impl DispatchTree { if source_node.parent != Some(*source_ancestor) { source_stack.pop(); self.pop_node(); + } else { + break; } } @@ -159,6 +161,7 @@ impl DispatchTree { } while !source_stack.is_empty() { + source_stack.pop(); self.pop_node(); } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 42183100b7c63ae2c5959a2b885afb3ba626d219..9c63f803254b8f099be1cb7c369b41458a253df2 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -1,9 +1,9 @@ use crate::{ - point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point, - ScaledPixels, StackingOrder, + point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels, + Point, ScaledPixels, StackingOrder, }; -use collections::BTreeMap; -use std::{fmt::Debug, iter::Peekable, mem, slice}; +use collections::{BTreeMap, FxHashSet}; +use std::{fmt::Debug, iter::Peekable, slice}; // Exported to metal pub(crate) type PointF = Point; @@ -14,7 +14,7 @@ pub type LayerId = u32; pub type DrawOrder = u32; #[derive(Default)] -pub(crate) struct SceneBuilder { +pub struct Scene { layers_by_order: BTreeMap, orders_by_layer: BTreeMap, shadows: Vec, @@ -26,57 +26,46 @@ pub(crate) struct SceneBuilder { surfaces: Vec, } -impl SceneBuilder { - pub fn build(&mut self) -> Scene { - let mut orders = vec![0; self.layers_by_order.len()]; - for (ix, layer_id) in self.layers_by_order.values().enumerate() { - orders[*layer_id as usize] = ix as u32; - } +impl Scene { + pub fn clear(&mut self) { self.layers_by_order.clear(); + self.orders_by_layer.clear(); + self.shadows.clear(); + self.quads.clear(); + self.paths.clear(); + self.underlines.clear(); + self.monochrome_sprites.clear(); + self.polychrome_sprites.clear(); + self.surfaces.clear(); + } - for shadow in &mut self.shadows { - shadow.order = orders[shadow.layer_id as usize]; - } - self.shadows.sort_by_key(|shadow| shadow.order); - - for quad in &mut self.quads { - quad.order = orders[quad.layer_id as usize]; - } - self.quads.sort_by_key(|quad| quad.order); - - for path in &mut self.paths { - path.order = orders[path.layer_id as usize]; - } - self.paths.sort_by_key(|path| path.order); - - for underline in &mut self.underlines { - underline.order = orders[underline.layer_id as usize]; - } - self.underlines.sort_by_key(|underline| underline.order); - - for monochrome_sprite in &mut self.monochrome_sprites { - monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize]; - } - self.monochrome_sprites.sort_by_key(|sprite| sprite.order); - - for polychrome_sprite in &mut self.polychrome_sprites { - polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize]; - } - self.polychrome_sprites.sort_by_key(|sprite| sprite.order); - - for surface in &mut self.surfaces { - surface.order = orders[surface.layer_id as usize]; - } - self.surfaces.sort_by_key(|surface| surface.order); + pub fn paths(&self) -> &[Path] { + &self.paths + } - Scene { - shadows: mem::take(&mut self.shadows), - quads: mem::take(&mut self.quads), - paths: mem::take(&mut self.paths), - underlines: mem::take(&mut self.underlines), - monochrome_sprites: mem::take(&mut self.monochrome_sprites), - polychrome_sprites: mem::take(&mut self.polychrome_sprites), - surfaces: mem::take(&mut self.surfaces), + pub(crate) fn batches(&self) -> impl Iterator { + BatchIterator { + shadows: &self.shadows, + shadows_start: 0, + shadows_iter: self.shadows.iter().peekable(), + quads: &self.quads, + quads_start: 0, + quads_iter: self.quads.iter().peekable(), + paths: &self.paths, + paths_start: 0, + paths_iter: self.paths.iter().peekable(), + underlines: &self.underlines, + underlines_start: 0, + underlines_iter: self.underlines.iter().peekable(), + monochrome_sprites: &self.monochrome_sprites, + monochrome_sprites_start: 0, + monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), + polychrome_sprites: &self.polychrome_sprites, + polychrome_sprites_start: 0, + polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(), + surfaces: &self.surfaces, + surfaces_start: 0, + surfaces_iter: self.surfaces.iter().peekable(), } } @@ -135,47 +124,98 @@ impl SceneBuilder { next_id } } -} -pub struct Scene { - pub shadows: Vec, - pub quads: Vec, - pub paths: Vec>, - pub underlines: Vec, - pub monochrome_sprites: Vec, - pub polychrome_sprites: Vec, - pub surfaces: Vec, -} + pub fn insert_views_from_scene(&mut self, views: &FxHashSet, prev_scene: &mut Self) { + for shadow in prev_scene.shadows.drain(..) { + if views.contains(&EntityId::from(shadow.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&shadow.layer_id]; + self.insert(&order, shadow); + } + } -impl Scene { - pub fn paths(&self) -> &[Path] { - &self.paths + for quad in prev_scene.quads.drain(..) { + if views.contains(&EntityId::from(quad.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&quad.layer_id]; + self.insert(&order, quad); + } + } + + for path in prev_scene.paths.drain(..) { + if views.contains(&EntityId::from(path.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&path.layer_id]; + self.insert(&order, path); + } + } + + for underline in prev_scene.underlines.drain(..) { + if views.contains(&EntityId::from(underline.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&underline.layer_id]; + self.insert(&order, underline); + } + } + + for sprite in prev_scene.monochrome_sprites.drain(..) { + if views.contains(&EntityId::from(sprite.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&sprite.layer_id]; + self.insert(&order, sprite); + } + } + + for sprite in prev_scene.polychrome_sprites.drain(..) { + if views.contains(&EntityId::from(sprite.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&sprite.layer_id]; + self.insert(&order, sprite); + } + } + + for surface in prev_scene.surfaces.drain(..) { + if views.contains(&EntityId::from(surface.view_id as u64)) { + let order = &prev_scene.orders_by_layer[&surface.layer_id]; + self.insert(&order, surface); + } + } } - pub(crate) fn batches(&self) -> impl Iterator { - BatchIterator { - shadows: &self.shadows, - shadows_start: 0, - shadows_iter: self.shadows.iter().peekable(), - quads: &self.quads, - quads_start: 0, - quads_iter: self.quads.iter().peekable(), - paths: &self.paths, - paths_start: 0, - paths_iter: self.paths.iter().peekable(), - underlines: &self.underlines, - underlines_start: 0, - underlines_iter: self.underlines.iter().peekable(), - monochrome_sprites: &self.monochrome_sprites, - monochrome_sprites_start: 0, - monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), - polychrome_sprites: &self.polychrome_sprites, - polychrome_sprites_start: 0, - polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(), - surfaces: &self.surfaces, - surfaces_start: 0, - surfaces_iter: self.surfaces.iter().peekable(), + pub fn finish(&mut self) { + let mut orders = vec![0; self.layers_by_order.len()]; + for (ix, layer_id) in self.layers_by_order.values().enumerate() { + orders[*layer_id as usize] = ix as u32; + } + + for shadow in &mut self.shadows { + shadow.order = orders[shadow.layer_id as usize]; + } + self.shadows.sort_by_key(|shadow| shadow.order); + + for quad in &mut self.quads { + quad.order = orders[quad.layer_id as usize]; + } + self.quads.sort_by_key(|quad| quad.order); + + for path in &mut self.paths { + path.order = orders[path.layer_id as usize]; + } + self.paths.sort_by_key(|path| path.order); + + for underline in &mut self.underlines { + underline.order = orders[underline.layer_id as usize]; + } + self.underlines.sort_by_key(|underline| underline.order); + + for monochrome_sprite in &mut self.monochrome_sprites { + monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize]; } + self.monochrome_sprites.sort_by_key(|sprite| sprite.order); + + for polychrome_sprite in &mut self.polychrome_sprites { + polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize]; + } + self.polychrome_sprites.sort_by_key(|sprite| sprite.order); + + for surface in &mut self.surfaces { + surface.order = orders[surface.layer_id as usize]; + } + self.surfaces.sort_by_key(|surface| surface.order); } } diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 24bc00ce39796890158bc6890e0fb779801a73b3..d049c472587fed22606e144bccc4d333ffed6558 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -231,7 +231,7 @@ impl AnyView { cx.with_absolute_element_offset(origin, |cx| { let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); cx.compute_layout(layout_id, available_space); - rendered_element.paint(cx); + cx.with_view_id(self.entity_id(), |cx| rendered_element.paint(cx)); }) } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8f9a82672da6a3c006d788a3e10782403f7c0a17..45dc197e18ae07f1234f86c01d6558d768b890c6 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -7,9 +7,9 @@ use crate::{ Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, Size, Style, - SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, - VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size, Style, SubscriberSet, + Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, + WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::{FxHashMap, FxHashSet}; @@ -289,7 +289,7 @@ pub(crate) struct Frame { pub(crate) element_states: FxHashMap, mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, - pub(crate) scene_builder: SceneBuilder, + pub(crate) scene: Scene, pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, pub(crate) z_index_stack: StackingOrder, pub(crate) next_stacking_order_id: u32, @@ -305,7 +305,7 @@ impl Frame { element_states: FxHashMap::default(), mouse_listeners: FxHashMap::default(), dispatch_tree, - scene_builder: SceneBuilder::default(), + scene: Scene::default(), z_index_stack: StackingOrder::default(), next_stacking_order_id: 0, depth_map: Default::default(), @@ -322,6 +322,7 @@ impl Frame { self.depth_map.clear(); self.next_stacking_order_id = 0; self.reused_views.clear(); + self.scene.clear(); } fn focus_path(&self) -> SmallVec<[FocusId; 8]> { @@ -1028,7 +1029,7 @@ impl<'a> WindowContext<'a> { let mut shadow_bounds = bounds; shadow_bounds.origin += shadow.offset; shadow_bounds.dilate(shadow.spread_radius); - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, Shadow { view_id: view_id.as_u64() as u32, @@ -1053,7 +1054,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, Quad { view_id: view_id.as_u64() as u32, @@ -1081,7 +1082,7 @@ impl<'a> WindowContext<'a> { let window = &mut *self.window; window .next_frame - .scene_builder + .scene .insert(&window.next_frame.z_index_stack, path.scale(scale_factor)); } @@ -1106,7 +1107,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, Underline { view_id: view_id.as_u64() as u32, @@ -1162,7 +1163,7 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, MonochromeSprite { view_id: view_id.as_u64() as u32, @@ -1216,7 +1217,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, PolychromeSprite { view_id: view_id.as_u64() as u32, @@ -1261,7 +1262,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, MonochromeSprite { view_id: view_id.as_u64() as u32, @@ -1300,7 +1301,7 @@ impl<'a> WindowContext<'a> { let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, PolychromeSprite { view_id: view_id.as_u64() as u32, @@ -1323,7 +1324,7 @@ impl<'a> WindowContext<'a> { let content_mask = self.content_mask().scale(scale_factor); let view_id = self.active_view_id(); let window = &mut *self.window; - window.next_frame.scene_builder.insert( + window.next_frame.scene.insert( &window.next_frame.z_index_stack, Surface { view_id: view_id.as_u64() as u32, @@ -1337,6 +1338,7 @@ impl<'a> WindowContext<'a> { } pub(crate) fn reuse_geometry(&mut self) { + println!("reusing geometry"); let view_id = self.active_view_id(); let window = &mut self.window; let grafted_view_ids = window @@ -1407,6 +1409,11 @@ impl<'a> WindowContext<'a> { }); } self.window.dirty_views.clear(); + self.window.next_frame.scene.insert_views_from_scene( + &self.window.next_frame.reused_views, + &mut self.window.rendered_frame.scene, + ); + self.window.next_frame.scene.finish(); self.window .next_frame @@ -1454,8 +1461,6 @@ impl<'a> WindowContext<'a> { .retain(&(), |listener| listener(&event, self)); } - let scene = self.window.rendered_frame.scene_builder.build(); - // Set the cursor only if we're the active window. let cursor_style = self .window @@ -1469,7 +1474,9 @@ impl<'a> WindowContext<'a> { self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); - self.window.platform_window.draw(&scene); + self.window + .platform_window + .draw(&self.window.rendered_frame.scene); } /// Dispatch a mouse or keyboard event on the window. From f57ff1c660d85e596dcbfa8237f8c7471822c7d7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 12:16:06 +0200 Subject: [PATCH 071/334] Make the scroll position updated as soon as possible to the correct deferred value Co-Authored-By: Antonio Scandurra --- crates/gpui/src/elements/uniform_list.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 244e8cde088be5942ad4a5887733b8b9795adbd7..793474f10b42cf429d66be2daaaa35b1cddc5a3d 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -95,6 +95,7 @@ impl UniformListScrollHandle { } else if item_bottom > scroll_top + state.list_height { scroll_offset.y = -(item_bottom - state.list_height); } + self.deferred_scroll_to_item = None; } else { self.deferred_scroll_to_item = Some(ix); } @@ -104,7 +105,6 @@ impl UniformListScrollHandle { if let Some(state) = &*self.state.borrow() { -state.scroll_offset.borrow().y } else { - self.deferred_scroll_to_item = Some(0); Pixels::ZERO } } @@ -241,12 +241,15 @@ impl Element for UniformList { scroll_handle.state.borrow_mut().replace(ScrollHandleState { item_height, list_height: padded_bounds.size.height, - scroll_offset: shared_scroll_offset, + scroll_offset: shared_scroll_offset.clone(), }); - if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { - dbg!("@@@@"); - scroll_handle.scroll_to_item(ix); - cx.notify(); + if let Some(scroll_handle) = self.scroll_handle.as_mut() { + if scroll_handle.state.borrow().is_some() { + if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { + scroll_handle.scroll_to_item(ix); + scroll_offset = *shared_scroll_offset.borrow(); + } + } } } From c197ea49ca0c4c975db323ccd509658e26f810af Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 13:44:08 +0200 Subject: [PATCH 072/334] Simplify uniform list scrolling logic --- crates/gpui/src/elements/uniform_list.rs | 76 ++++-------- crates/language_tools/src/syntax_tree_view.rs | 112 ++++++++---------- 2 files changed, 67 insertions(+), 121 deletions(-) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 793474f10b42cf429d66be2daaaa35b1cddc5a3d..77ef7df10c206c35ff59d354e1c90c88e7cbec5f 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -1,7 +1,7 @@ use crate::{ point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId, - Pixels, Point, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, + Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -65,48 +65,18 @@ pub struct UniformList { #[derive(Clone, Default)] pub struct UniformListScrollHandle { - state: Rc>>, - deferred_scroll_to_item: Option, -} - -#[derive(Clone, Debug)] -struct ScrollHandleState { - item_height: Pixels, - list_height: Pixels, - scroll_offset: Rc>>, + deferred_scroll_to_item: Rc>>, } impl UniformListScrollHandle { pub fn new() -> Self { Self { - state: Rc::new(RefCell::new(None)), - deferred_scroll_to_item: None, + deferred_scroll_to_item: Rc::new(RefCell::new(None)), } } pub fn scroll_to_item(&mut self, ix: usize) { - if let Some(state) = &*self.state.borrow() { - let mut scroll_offset = state.scroll_offset.borrow_mut(); - let item_top = state.item_height * ix; - let item_bottom = item_top + state.item_height; - let scroll_top = -scroll_offset.y; - if item_top < scroll_top { - scroll_offset.y = -item_top; - } else if item_bottom > scroll_top + state.list_height { - scroll_offset.y = -(item_bottom - state.list_height); - } - self.deferred_scroll_to_item = None; - } else { - self.deferred_scroll_to_item = Some(ix); - } - } - - pub fn scroll_top(&mut self) -> Pixels { - if let Some(state) = &*self.state.borrow() { - -state.scroll_offset.borrow().y - } else { - Pixels::ZERO - } + self.deferred_scroll_to_item.replace(Some(ix)); } } @@ -199,18 +169,14 @@ impl Element for UniformList { let shared_scroll_offset = element_state .interactive .scroll_offset - .get_or_insert_with(|| { - if let Some(scroll_handle) = self.scroll_handle.as_ref() { - if let Some(scroll_handle) = scroll_handle.state.borrow().as_ref() { - return scroll_handle.scroll_offset.clone(); - } - } - - Rc::default() - }) + .get_or_insert_with(|| Rc::default()) .clone(); let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height; + let shared_scroll_to_item = self + .scroll_handle + .as_mut() + .and_then(|handle| handle.deferred_scroll_to_item.take()); self.interactivity.paint( bounds, @@ -237,20 +203,18 @@ impl Element for UniformList { scroll_offset.y = min_scroll_offset; } - if let Some(scroll_handle) = self.scroll_handle.as_mut() { - scroll_handle.state.borrow_mut().replace(ScrollHandleState { - item_height, - list_height: padded_bounds.size.height, - scroll_offset: shared_scroll_offset.clone(), - }); - if let Some(scroll_handle) = self.scroll_handle.as_mut() { - if scroll_handle.state.borrow().is_some() { - if let Some(ix) = scroll_handle.deferred_scroll_to_item.take() { - scroll_handle.scroll_to_item(ix); - scroll_offset = *shared_scroll_offset.borrow(); - } - } + if let Some(ix) = shared_scroll_to_item { + let list_height = padded_bounds.size.height; + let mut updated_scroll_offset = shared_scroll_offset.borrow_mut(); + let item_top = item_height * ix; + let item_bottom = item_top + item_height; + let scroll_top = -updated_scroll_offset.y; + if item_top < scroll_top { + updated_scroll_offset.y = -item_top; + } else if item_bottom > scroll_top + list_height { + updated_scroll_offset.y = -(item_bottom - list_height); } + scroll_offset = *updated_scroll_offset; } let first_visible_element_ix = diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index c30564e9bfe3f8c0e0fd1f5c2f6827a4f070fd81..b49e4eb649ae28fea72ad2d83402653961caf0b8 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -2,13 +2,12 @@ use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId}; use gpui::{ actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div, EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, - MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, Render, Styled, + MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext, }; use language::{Buffer, OwnedSyntaxLayerInfo}; -use settings::Settings; use std::{mem, ops::Range}; -use theme::{ActiveTheme, ThemeSettings}; +use theme::ActiveTheme; use tree_sitter::{Node, TreeCursor}; use ui::{h_stack, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu}; use workspace::{ @@ -34,8 +33,6 @@ pub fn init(cx: &mut AppContext) { pub struct SyntaxTreeView { workspace_handle: WeakView, editor: Option, - mouse_y: Option, - line_height: Option, list_scroll_handle: UniformListScrollHandle, selected_descendant_ix: Option, hovered_descendant_ix: Option, @@ -70,8 +67,6 @@ impl SyntaxTreeView { workspace_handle: workspace_handle.clone(), list_scroll_handle: UniformListScrollHandle::new(), editor: None, - mouse_y: None, - line_height: None, hovered_descendant_ix: None, selected_descendant_ix: None, focus_handle: cx.focus_handle(), @@ -208,39 +203,6 @@ impl SyntaxTreeView { Some(()) } - fn handle_click(&mut self, y: Pixels, cx: &mut ViewContext) -> Option<()> { - let line_height = self.line_height?; - let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize; - - self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, mut range, cx| { - // Put the cursor at the beginning of the node. - mem::swap(&mut range.start, &mut range.end); - - editor.change_selections(Some(Autoscroll::newest()), cx, |selections| { - selections.select_ranges(vec![range]); - }); - }); - Some(()) - } - - fn hover_state_changed(&mut self, cx: &mut ViewContext) { - if let Some((y, line_height)) = self.mouse_y.zip(self.line_height) { - let ix = ((self.list_scroll_handle.scroll_top() + y) / line_height) as usize; - if self.hovered_descendant_ix != Some(ix) { - self.hovered_descendant_ix = Some(ix); - self.update_editor_with_range_for_descendant_ix(ix, cx, |editor, range, cx| { - editor.clear_background_highlights::(cx); - editor.highlight_background::( - vec![range], - |theme| theme.editor_document_highlight_write_background, - cx, - ); - }); - cx.notify(); - } - } - } - fn update_editor_with_range_for_descendant_ix( &self, descendant_ix: usize, @@ -306,15 +268,6 @@ impl SyntaxTreeView { impl Render for SyntaxTreeView { fn render(&mut self, cx: &mut gpui::ViewContext<'_, Self>) -> impl IntoElement { - let settings = ThemeSettings::get_global(cx); - let line_height = cx - .text_style() - .line_height_in_pixels(settings.buffer_font_size(cx)); - if Some(line_height) != self.line_height { - self.line_height = Some(line_height); - self.hover_state_changed(cx); - } - let mut rendered = div().flex_1(); if let Some(layer) = self @@ -345,12 +298,51 @@ impl Render for SyntaxTreeView { break; } } else { - items.push(Self::render_node( - &cursor, - depth, - Some(descendant_ix) == this.selected_descendant_ix, - cx, - )); + items.push( + Self::render_node( + &cursor, + depth, + Some(descendant_ix) == this.selected_descendant_ix, + cx, + ) + .on_mouse_down( + MouseButton::Left, + cx.listener(move |tree_view, _: &MouseDownEvent, cx| { + tree_view.update_editor_with_range_for_descendant_ix( + descendant_ix, + cx, + |editor, mut range, cx| { + // Put the cursor at the beginning of the node. + mem::swap(&mut range.start, &mut range.end); + + editor.change_selections( + Some(Autoscroll::newest()), + cx, + |selections| { + selections.select_ranges(vec![range]); + }, + ); + }, + ); + }), + ) + .on_mouse_move(cx.listener( + move |tree_view, _: &MouseMoveEvent, cx| { + if tree_view.hovered_descendant_ix != Some(descendant_ix) { + tree_view.hovered_descendant_ix = Some(descendant_ix); + tree_view.update_editor_with_range_for_descendant_ix(descendant_ix, cx, |editor, range, cx| { + editor.clear_background_highlights::(cx); + editor.highlight_background::( + vec![range], + |theme| theme.editor_document_highlight_write_background, + cx, + ); + }); + cx.notify(); + } + }, + )), + ); descendant_ix += 1; if cursor.goto_first_child() { depth += 1; @@ -364,16 +356,6 @@ impl Render for SyntaxTreeView { ) .size_full() .track_scroll(self.list_scroll_handle.clone()) - .on_mouse_move(cx.listener(move |tree_view, event: &MouseMoveEvent, cx| { - tree_view.mouse_y = Some(event.position.y); - tree_view.hover_state_changed(cx); - })) - .on_mouse_down( - MouseButton::Left, - cx.listener(move |tree_view, event: &MouseDownEvent, cx| { - tree_view.handle_click(event.position.y, cx); - }), - ) .text_bg(cx.theme().colors().background); rendered = rendered.child( From 1c7710405090c1acd7ad7d69dcefee1fa15feefe Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:43:49 +0100 Subject: [PATCH 073/334] chore: Enable asset compression This reduces size of release binary by ~20% from 134MB to 107MB without noticeable slowdown on startup. Assets are decompressed granularly, on first access --- Cargo.lock | 67 ++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- crates/semantic_index/Cargo.toml | 2 +- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe78434a7accf9852dfd45275902402adaaf5040..f065290e1430befc01cf3b0ce4fed347bda0ee07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3440,6 +3440,40 @@ dependencies = [ "tiff", ] +[[package]] +name = "include-flate" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e11569346406931d20276cc460215ee2826e7cad43aa986999cb244dd7adb0" +dependencies = [ + "include-flate-codegen-exports", + "lazy_static", + "libflate", +] + +[[package]] +name = "include-flate-codegen" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a7d6e1419fa3129eb0802b4c99603c0d425c79fb5d76191d5a20d0ab0d664e8" +dependencies = [ + "libflate", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include-flate-codegen-exports" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75657043ffe3d8280f1cb8aef0f505532b392ed7758e0baeac22edadcee31a03" +dependencies = [ + "include-flate-codegen", + "proc-macro-hack", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -3831,6 +3865,26 @@ version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +[[package]] +name = "libflate" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" +dependencies = [ + "adler32", + "crc32fast", + "libflate_lz77", +] + +[[package]] +name = "libflate_lz77" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" +dependencies = [ + "rle-decode-fast", +] + [[package]] name = "libgit2-sys" version = "0.14.2+1.5.1" @@ -5408,6 +5462,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.67" @@ -6102,6 +6162,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + [[package]] name = "rmp" version = "0.8.12" @@ -6249,6 +6315,7 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" dependencies = [ + "include-flate", "rust-embed-impl", "rust-embed-utils", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 79d28821d4b040f35fc1ab1dd391cddeee93bc47..7ea79f094c0b6d5d5cd4a1e63f70059db05ec6e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ prost = { version = "0.8" } rand = { version = "0.8.5" } refineable = { path = "./crates/refineable" } regex = { version = "1.5" } -rust-embed = { version = "8.0", features = ["include-exclude"] } +rust-embed = { version = "8.0", features = ["include-exclude", "compression"] } rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } schemars = { version = "0.8" } serde = { version = "1.0", features = ["derive", "rc"] } diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index a437a596a526de3878c35a1d409bb3f11dd7afee..41a3ab2d579867e602746775e55d68496d6a5387 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -47,7 +47,7 @@ project = { path = "../project", features = ["test-support"] } rpc = { path = "../rpc", features = ["test-support"] } workspace = { path = "../workspace", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"]} -rust-embed = { version = "8.0", features = ["include-exclude"] } +rust-embed.workspace = true client = { path = "../client" } node_runtime = { path = "../node_runtime"} From d0c101cb6e5147ca9688e30afa34a26400c57fae Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 Jan 2024 15:00:40 +0100 Subject: [PATCH 074/334] Reuse depth map entries and retain element states for cached trees --- crates/gpui/src/key_dispatch.rs | 34 ++-- crates/gpui/src/view.rs | 46 ++--- crates/gpui/src/window.rs | 307 ++++++++++++++++++-------------- 3 files changed, 211 insertions(+), 176 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 168b1da9fe7e54d9a36982deaed8972dd8b5c482..a4a048a930fee5bec2918d54787416c7aa6c9ddc 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -18,7 +18,6 @@ pub struct DispatchNodeId(usize); pub(crate) struct DispatchTree { node_stack: Vec, pub(crate) context_stack: Vec, - view_stack: Vec, nodes: Vec, focusable_node_ids: FxHashMap, view_node_ids: FxHashMap, @@ -50,7 +49,6 @@ impl DispatchTree { Self { node_stack: Vec::new(), context_stack: Vec::new(), - view_stack: Vec::new(), nodes: Vec::new(), focusable_node_ids: FxHashMap::default(), view_node_ids: FxHashMap::default(), @@ -63,7 +61,6 @@ impl DispatchTree { pub fn clear(&mut self) { self.node_stack.clear(); self.context_stack.clear(); - self.view_stack.clear(); self.nodes.clear(); self.focusable_node_ids.clear(); self.view_node_ids.clear(); @@ -76,6 +73,15 @@ impl DispatchTree { focus_id: Option, view_id: Option, ) { + // Associate a view id to this only if it is the root node for the view. + let view_id = view_id.and_then(|view_id| { + if self.view_node_ids.contains_key(&view_id) { + None + } else { + Some(view_id) + } + }); + let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); self.nodes.push(DispatchNode { @@ -96,7 +102,6 @@ impl DispatchTree { } if let Some(view_id) = view_id { - self.view_stack.push(view_id); self.view_node_ids.insert(view_id, node_id); } } @@ -106,21 +111,14 @@ impl DispatchTree { if node.context.is_some() { self.context_stack.pop(); } - if node.view_id.is_some() { - self.view_stack.pop(); - } self.node_stack.pop(); } - fn move_node(&mut self, source_node: &mut DispatchNode) { - self.push_node( - source_node.context.take(), - source_node.focus_id, - source_node.view_id, - ); - let target_node = self.active_node(); - target_node.key_listeners = mem::take(&mut source_node.key_listeners); - target_node.action_listeners = mem::take(&mut source_node.action_listeners); + fn move_node(&mut self, source: &mut DispatchNode) { + self.push_node(source.context.take(), source.focus_id, source.view_id); + let target = self.active_node(); + target.key_listeners = mem::take(&mut source.key_listeners); + target.action_listeners = mem::take(&mut source.action_listeners); } pub fn graft(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { @@ -354,10 +352,6 @@ impl DispatchTree { view_path } - pub fn active_view_id(&self) -> Option { - self.view_stack.last().copied() - } - pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode { &self.nodes[node_id.0] } diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index d049c472587fed22606e144bccc4d333ffed6558..6ec78e0d2e8bd62dc5e30d7a47ba77cd254cb892 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -89,9 +89,11 @@ impl Element for View { _state: Option, cx: &mut WindowContext, ) -> (LayoutId, Self::State) { - let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); - let layout_id = element.request_layout(cx); - (layout_id, Some(element)) + cx.with_view_id(self.entity_id(), |cx| { + let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element()); + let layout_id = element.request_layout(cx); + (layout_id, Some(element)) + }) } fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { @@ -228,10 +230,12 @@ impl AnyView { available_space: Size, cx: &mut WindowContext, ) { - cx.with_absolute_element_offset(origin, |cx| { - let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); - cx.compute_layout(layout_id, available_space); - cx.with_view_id(self.entity_id(), |cx| rendered_element.paint(cx)); + cx.with_view_id(self.entity_id(), |cx| { + cx.with_absolute_element_offset(origin, |cx| { + let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); + cx.compute_layout(layout_id, available_space); + rendered_element.paint(cx) + }); }) } } @@ -254,21 +258,23 @@ impl Element for AnyView { state: Option, cx: &mut WindowContext, ) -> (LayoutId, Self::State) { - if self.cache { - if let Some(state) = state { - let layout_id = cx.request_layout(&state.root_style, None); - return (layout_id, state); + cx.with_view_id(self.entity_id(), |cx| { + if self.cache { + if let Some(state) = state { + let layout_id = cx.request_layout(&state.root_style, None); + return (layout_id, state); + } } - } - let (layout_id, element) = (self.request_layout)(self, cx); - let root_style = cx.layout_style(layout_id).unwrap().clone(); - let state = AnyViewState { - root_style, - cache_key: None, - element: Some(element), - }; - (layout_id, state) + let (layout_id, element) = (self.request_layout)(self, cx); + let root_style = cx.layout_style(layout_id).unwrap().clone(); + let state = AnyViewState { + root_style, + cache_key: None, + element: Some(element), + }; + (layout_id, state) + }) } fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 45dc197e18ae07f1234f86c01d6558d768b890c6..376f4c24662f39a766c9aaa3ce3b2c69a4f75f9c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -280,6 +280,7 @@ pub struct Window { pub(crate) struct ElementStateBox { inner: Box, + parent_view_id: EntityId, #[cfg(debug_assertions)] type_name: &'static str, } @@ -290,11 +291,12 @@ pub(crate) struct Frame { mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, pub(crate) scene: Scene, - pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, + pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, pub(crate) z_index_stack: StackingOrder, pub(crate) next_stacking_order_id: u32, content_mask_stack: Vec>, element_offset_stack: Vec>, + pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, } @@ -311,6 +313,7 @@ impl Frame { depth_map: Default::default(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), + view_stack: Vec::new(), reused_views: FxHashSet::default(), } } @@ -323,6 +326,7 @@ impl Frame { self.next_stacking_order_id = 0; self.reused_views.clear(); self.scene.clear(); + debug_assert_eq!(self.view_stack.len(), 0); } fn focus_path(&self) -> SmallVec<[FocusId; 8]> { @@ -880,7 +884,7 @@ impl<'a> WindowContext<'a> { &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let order = self.window.next_frame.z_index_stack.clone(); self.window .next_frame @@ -967,17 +971,18 @@ impl<'a> WindowContext<'a> { /// Called during painting to track which z-index is on top at each pixel position pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); - let depth_map = &mut self.window.next_frame.depth_map; - match depth_map.binary_search_by(|(level, _)| stacking_order.cmp(level)) { - Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, bounds)), - } + let view_id = self.parent_view_id().unwrap(); + self.window + .next_frame + .depth_map + .push((stacking_order, view_id, bounds)); } /// Returns true if there is no opaque layer containing the given point /// on top of the given level. Layers whose level is an extension of the /// level are not considered to be on top of the level. pub fn was_top_layer(&self, point: &Point, level: &StackingOrder) -> bool { - for (opaque_level, bounds) in self.window.rendered_frame.depth_map.iter() { + for (opaque_level, _, bounds) in self.window.rendered_frame.depth_map.iter() { if level >= opaque_level { break; } @@ -994,7 +999,7 @@ impl<'a> WindowContext<'a> { point: &Point, level: &StackingOrder, ) -> bool { - for (opaque_level, bounds) in self.window.rendered_frame.depth_map.iter() { + for (opaque_level, _, bounds) in self.window.rendered_frame.depth_map.iter() { if level >= opaque_level { break; } @@ -1023,7 +1028,7 @@ impl<'a> WindowContext<'a> { ) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; for shadow in shadows { let mut shadow_bounds = bounds; @@ -1051,7 +1056,7 @@ impl<'a> WindowContext<'a> { pub fn paint_quad(&mut self, quad: PaintQuad) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1074,7 +1079,7 @@ impl<'a> WindowContext<'a> { pub fn paint_path(&mut self, mut path: Path, color: impl Into) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); path.content_mask = content_mask; path.color = color.into(); @@ -1104,7 +1109,7 @@ impl<'a> WindowContext<'a> { size: size(width, height), }; let content_mask = self.content_mask(); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1161,7 +1166,7 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( &window.next_frame.z_index_stack, @@ -1214,7 +1219,7 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1259,7 +1264,7 @@ impl<'a> WindowContext<'a> { Ok((params.size, Cow::Owned(bytes))) })?; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1298,7 +1303,7 @@ impl<'a> WindowContext<'a> { })?; let content_mask = self.content_mask().scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1322,7 +1327,7 @@ impl<'a> WindowContext<'a> { let scale_factor = self.scale_factor(); let bounds = bounds.scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut *self.window; window.next_frame.scene.insert( &window.next_frame.z_index_stack, @@ -1338,8 +1343,7 @@ impl<'a> WindowContext<'a> { } pub(crate) fn reuse_geometry(&mut self) { - println!("reusing geometry"); - let view_id = self.active_view_id(); + let view_id = self.parent_view_id().unwrap(); let window = &mut self.window; let grafted_view_ids = window .next_frame @@ -1350,17 +1354,8 @@ impl<'a> WindowContext<'a> { } } - fn active_view_id(&self) -> EntityId { - self.window - .next_frame - .dispatch_tree - .active_view_id() - .expect("a view should always be active") - } - /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) { - println!("====================="); self.window.dirty = false; self.window.drawing = true; @@ -1409,11 +1404,6 @@ impl<'a> WindowContext<'a> { }); } self.window.dirty_views.clear(); - self.window.next_frame.scene.insert_views_from_scene( - &self.window.next_frame.reused_views, - &mut self.window.rendered_frame.scene, - ); - self.window.next_frame.scene.finish(); self.window .next_frame @@ -1425,6 +1415,7 @@ impl<'a> WindowContext<'a> { self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); + // Reuse mouse listeners that didn't change since the last frame. for (type_id, listeners) in &mut self.window.rendered_frame.mouse_listeners { let next_listeners = self .window @@ -1439,6 +1430,43 @@ impl<'a> WindowContext<'a> { } } + // Reuse entries in the depth map that didn't change since the last frame. + for (order, view_id, bounds) in self.window.rendered_frame.depth_map.drain(..) { + if self.window.next_frame.reused_views.contains(&view_id) { + self.window + .next_frame + .depth_map + .push((order, view_id, bounds)); + } + } + self.window + .next_frame + .depth_map + .sort_by(|a, b| a.0.cmp(&b.0)); + + // Retain element states for views that didn't change since the last frame. + for (element_id, state) in self.window.rendered_frame.element_states.drain() { + if self + .window + .next_frame + .reused_views + .contains(&state.parent_view_id) + { + self.window + .next_frame + .element_states + .entry(element_id) + .or_insert(state); + } + } + + // Reuse geometry that didn't change since the last frame. + self.window.next_frame.scene.insert_views_from_scene( + &self.window.next_frame.reused_views, + &mut self.window.rendered_frame.scene, + ); + self.window.next_frame.scene.finish(); + let previous_focus_path = self.window.rendered_frame.focus_path(); mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); let current_focus_path = self.window.rendered_frame.focus_path(); @@ -1871,12 +1899,13 @@ impl<'a> WindowContext<'a> { focus_handle: Option, f: impl FnOnce(Option, &mut Self) -> R, ) -> R { + let parent_view_id = self.parent_view_id(); let window = &mut self.window; let focus_id = focus_handle.as_ref().map(|handle| handle.id); window .next_frame .dispatch_tree - .push_node(context.clone(), focus_id, None); + .push_node(context.clone(), focus_id, parent_view_id); let result = f(focus_handle, self); @@ -1885,6 +1914,114 @@ impl<'a> WindowContext<'a> { result } + pub(crate) fn with_view_id( + &mut self, + view_id: EntityId, + f: impl FnOnce(&mut Self) -> R, + ) -> R { + self.window.next_frame.view_stack.push(view_id); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + } + + /// Update or initialize state for an element with the given id that lives across multiple + /// frames. If an element with this id existed in the rendered frame, its state will be passed + /// to the given closure. The state returned by the closure will be stored so it can be referenced + /// when drawing the next frame. + pub(crate) fn with_element_state( + &mut self, + id: ElementId, + f: impl FnOnce(Option, &mut Self) -> (R, S), + ) -> R + where + S: 'static, + { + self.with_element_id(Some(id), |cx| { + let global_id = cx.window().element_id_stack.clone(); + + if let Some(any) = cx + .window_mut() + .next_frame + .element_states + .remove(&global_id) + .or_else(|| { + cx.window_mut() + .rendered_frame + .element_states + .remove(&global_id) + }) + { + let ElementStateBox { + inner, + parent_view_id, + #[cfg(debug_assertions)] + type_name + } = any; + // Using the extra inner option to avoid needing to reallocate a new box. + let mut state_box = inner + .downcast::>() + .map_err(|_| { + #[cfg(debug_assertions)] + { + anyhow!( + "invalid element state type for id, requested_type {:?}, actual type: {:?}", + std::any::type_name::(), + type_name + ) + } + + #[cfg(not(debug_assertions))] + { + anyhow!( + "invalid element state type for id, requested_type {:?}", + std::any::type_name::(), + ) + } + }) + .unwrap(); + + // Actual: Option <- View + // Requested: () <- AnyElemet + let state = state_box + .take() + .expect("element state is already on the stack"); + let (result, state) = f(Some(state), cx); + state_box.replace(state); + cx.window_mut() + .next_frame + .element_states + .insert(global_id, ElementStateBox { + inner: state_box, + parent_view_id, + #[cfg(debug_assertions)] + type_name + }); + result + } else { + let (result, state) = f(None, cx); + let parent_view_id = cx.parent_view_id().unwrap(); + cx.window_mut() + .next_frame + .element_states + .insert(global_id, + ElementStateBox { + inner: Box::new(Some(state)), + parent_view_id, + #[cfg(debug_assertions)] + type_name: std::any::type_name::() + } + + ); + result + } + }) + } + + fn parent_view_id(&self) -> Option { + self.window.next_frame.view_stack.last().copied() + } + /// Set an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the /// platform to receive textual input with proper integration with concerns such /// as IME interactions. @@ -2169,16 +2306,6 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { result } - fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - self.window_mut() - .next_frame - .dispatch_tree - .push_node(None, None, Some(view_id)); - let result = f(self); - self.window_mut().next_frame.dispatch_tree.pop_node(); - result - } - /// Update the global element offset relative to the current offset. This is used to implement /// scrolling. fn with_element_offset( @@ -2220,98 +2347,6 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { .unwrap_or_default() } - /// Update or initialize state for an element with the given id that lives across multiple - /// frames. If an element with this id existed in the rendered frame, its state will be passed - /// to the given closure. The state returned by the closure will be stored so it can be referenced - /// when drawing the next frame. - fn with_element_state( - &mut self, - id: ElementId, - f: impl FnOnce(Option, &mut Self) -> (R, S), - ) -> R - where - S: 'static, - { - self.with_element_id(Some(id), |cx| { - let global_id = cx.window().element_id_stack.clone(); - - if let Some(any) = cx - .window_mut() - .next_frame - .element_states - .remove(&global_id) - .or_else(|| { - cx.window_mut() - .rendered_frame - .element_states - .remove(&global_id) - }) - { - let ElementStateBox { - inner, - - #[cfg(debug_assertions)] - type_name - } = any; - // Using the extra inner option to avoid needing to reallocate a new box. - let mut state_box = inner - .downcast::>() - .map_err(|_| { - #[cfg(debug_assertions)] - { - anyhow!( - "invalid element state type for id, requested_type {:?}, actual type: {:?}", - std::any::type_name::(), - type_name - ) - } - - #[cfg(not(debug_assertions))] - { - anyhow!( - "invalid element state type for id, requested_type {:?}", - std::any::type_name::(), - ) - } - }) - .unwrap(); - - // Actual: Option <- View - // Requested: () <- AnyElemet - let state = state_box - .take() - .expect("element state is already on the stack"); - let (result, state) = f(Some(state), cx); - state_box.replace(state); - cx.window_mut() - .next_frame - .element_states - .insert(global_id, ElementStateBox { - inner: state_box, - - #[cfg(debug_assertions)] - type_name - }); - result - } else { - let (result, state) = f(None, cx); - cx.window_mut() - .next_frame - .element_states - .insert(global_id, - ElementStateBox { - inner: Box::new(Some(state)), - - #[cfg(debug_assertions)] - type_name: std::any::type_name::() - } - - ); - result - } - }) - } - /// Obtain the current content mask. fn content_mask(&self) -> ContentMask { self.window() From aff119b80a21e3c3bdaf65d0ec55360da49d398b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 10 Jan 2024 10:09:48 -0500 Subject: [PATCH 075/334] Fix possessive "its" in docs and comments (#3998) This PR fixes a number of places where we were incorrectly using "it's" where we needed to use the possessive "its". Release Notes: - N/A --- crates/editor/src/editor.rs | 2 +- crates/gpui/src/action.rs | 2 +- crates/gpui/src/color.rs | 2 +- crates/gpui/src/elements/overlay.rs | 2 +- crates/plugin_runtime/OPAQUE.md | 34 ++++++++++----------- crates/plugin_runtime/README.md | 14 ++++----- crates/rope/src/rope.rs | 2 +- crates/search/src/buffer_search.rs | 2 +- crates/semantic_index/src/semantic_index.rs | 2 +- crates/terminal/src/terminal.rs | 8 ++--- crates/ui/docs/hello-world.md | 2 +- crates/ui/src/components/checkbox.rs | 2 +- crates/ui/src/disableable.rs | 2 +- crates/util/src/util.rs | 8 ++--- docs/src/developing_zed__building_zed.md | 2 +- 15 files changed, 43 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 95aec1a2007e19df4eb3099bff0f104bd4754a80..66687377bda8d9d217ea6c5acc9b6c5dc6acc186 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7050,7 +7050,7 @@ impl Editor { let buffer = self.buffer.read(cx).snapshot(cx); let selection = self.selections.newest::(cx); - // If there is an active Diagnostic Popover. Jump to it's diagnostic instead. + // If there is an active Diagnostic Popover jump to its diagnostic instead. if direction == Direction::Next { if let Some(popover) = self.hover_state.diagnostic_popover.as_ref() { let (group_id, jump_to) = popover.activation_info(); diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index 04e6ccdbfa6f3217306cd795ef9f142c4b54e68f..ef02316f83ea9dfa57c68106f6b5706b755e3cd6 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -104,7 +104,7 @@ pub struct ActionData { } /// This constant must be public to be accessible from other crates. -/// But it's existence is an implementation detail and should not be used directly. +/// But its existence is an implementation detail and should not be used directly. #[doc(hidden)] #[linkme::distributed_slice] pub static __GPUI_ACTIONS: [MacroActionBuilder]; diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index dc0f5055e70a123b99a7cc8b746ee69bd23652a3..bc764e564c3340957f947ef669243be0a7d27e4d 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -321,7 +321,7 @@ impl Hsla { /// /// Assumptions: /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent. - /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value. + /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing its own alpha value. /// - RGB color components are contained in the range [0, 1]. /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined. pub fn blend(self, other: Hsla) -> Hsla { diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index 019436493d937bdc5898db695622f53ff9d8f997..6996a13cfaac61b84e9ba7df10f240710ca2d162 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -45,7 +45,7 @@ impl Overlay { } /// Sets the position mode for this overlay. Local will have this - /// interpret it's [Overlay::position] as relative to the parent element. + /// interpret its [`Overlay::position`] as relative to the parent element. /// While Window will have it interpret the position as relative to the window. pub fn position_mode(mut self, mode: OverlayPositionMode) -> Self { self.position_mode = mode; diff --git a/crates/plugin_runtime/OPAQUE.md b/crates/plugin_runtime/OPAQUE.md index 52ee75dbf9bab78811d0ad858d2036889d9a0517..4d38409ec2629b73aaa83287c4bb3c522ea842f0 100644 --- a/crates/plugin_runtime/OPAQUE.md +++ b/crates/plugin_runtime/OPAQUE.md @@ -34,7 +34,7 @@ Rhai actually exposes a pretty nice interface for working with native Rust types > **Note**: Rhai uses strings, but I wonder if you could get away with something more compact using `TypeIds`. Maybe not, given that `TypeId`s are not deterministic across builds, and we'd need matching IDs both host-side and guest side. -In Rhai, we can alternatively use the method `Engine::register_type_with_name::(name: &str)` if we have a different type name host-side (in Rust) and guest-side (in Rhai). +In Rhai, we can alternatively use the method `Engine::register_type_with_name::(name: &str)` if we have a different type name host-side (in Rust) and guest-side (in Rhai). With respect to Wasm plugins, I think an interface like this is fairly important, because we don't know whether the original plugin was written in Rust. (This may not be true now, because we write all the plugins Zed uses, but once we allow packaging and shipping plugins, it's important to maintain a consistent interface, because even Rust changes over time.) @@ -72,15 +72,15 @@ Union::Variant(v, ..) => (*v).as_boxed_any().downcast().ok().map(|x| *x), Now Rhai can do this because it's implemented in Rust. In other words, unlike Wasm, Rhai scripts can, indirectly, hold references to places in host memory. For us to implement something like this for Wasm plugins, we'd have to keep track of a "`ResourcePool`"—alive for the duration of each function call—that we can check rust types into and out of. I think I've got a handle on how Rhai works now, so let's stop talking about Rhai and discuss what this opaque object system would look like if we implemented it in Rust. - + # Design Sketch - + First things first, we'd have to generalize the arguments we can pass to and return from functions host-side. Currently, we support anything that's `serde`able. We'd have to create a new trait, say `Value`, that has blanket implementations for both `serde` and `Clone` (or something like this; if a type is both `serde` and `clone`, we'd have to figure out a way to disambiguate). - - We'd also create a `ResourcePool` struct that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources (e.g. `Clone` instead of `serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. - + + We'd also create a `ResourcePool` struct that essentially is a `Vec` of `Box`. When calling a function, all `Value` arguments that are resources (e.g. `Clone` instead of `serde`) would be typecasted to `dyn Any` and stored in the `ResourcePool`. + We'd probably also need a `Resource` trait that defines an associated handle for a resource. Something like this: - + ```rust pub trait Resource { type Handle: Serialize + DeserializeOwned; @@ -88,24 +88,24 @@ First things first, we'd have to generalize the arguments we can pass to and ret fn index(handle: Self) -> u32; } ``` - + Where a handle is just a dead-simple wrapper around a `u32`: - - ```rust + + ```rust #[derive(Serialize, Deserialize)] pub struct CoolHandle(u32); ``` - + It's important that this handle be accessible *both* host-side and plugin side. I don't know if this means that we have another crate, like `plugin_handles`, that contains a bunch of u32 wrappers, or something else. Because a `Resource::Handle` is just a u32, it's trivially `serde`, and can cross the ABI boundary. - - So when we add each `T: Resource` to the `ResourcePool`, the resource pool typecasts it to `Any`, appends it to the `Vec`, and returns the associated `Resource::Handle`. This handle is what we pass through to Wasm. - + + So when we add each `T: Resource` to the `ResourcePool`, the resource pool typecasts it to `Any`, appends it to the `Vec`, and returns the associated `Resource::Handle`. This handle is what we pass through to Wasm. + ```rust // Implementations and attributes omitted pub struct Rope { ... }; pub struct RopeHandle(u32); impl Resource for Arc> { ... } - + let builder: PluginBuilder = ...; let builder = builder .host_fn_async( @@ -127,7 +127,7 @@ use plugin_handles::RopeHandle; pub fn append(rope: RopeHandle, string: &str); ``` -This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only acquire resources to handles we're given, so we'd need to expose a function that takes a handle. +This allows us to perform an operation on a `Rope`, but how do we get a `RopeHandle` into a plugin? Well, as plugins, we can only acquire resources to handles we're given, so we'd need to expose a function that takes a handle. To illustrate that point, here's an example. First, we'd define a plugin-side function as follows: @@ -185,4 +185,4 @@ Using this approach, it should be possible to add fairly good support for resour This next week, I'll try to get a production-ready version of this working, using the `Language` resource required by some Language Server Adapters. -Hope this guide made sense! \ No newline at end of file +Hope this guide made sense! diff --git a/crates/plugin_runtime/README.md b/crates/plugin_runtime/README.md index 38d1c0bb5d1d2bc44f0c150c8667774a998befaf..25524dd2721ac1b6bf8580f2c062a56bb1d835ed 100644 --- a/crates/plugin_runtime/README.md +++ b/crates/plugin_runtime/README.md @@ -164,7 +164,7 @@ To call the functions that a plugin exports host-side, you need to have 'handles For example, let's suppose we're creating a plugin that: -1. formats a message +1. formats a message 2. processes a list of numbers somehow We could create a struct for this plugin as follows: @@ -179,7 +179,7 @@ pub struct CoolPlugin { } ``` -Note that this plugin also holds an owned reference to the runtime, which is stored in the `Plugin` type. In asynchronous or multithreaded contexts, it may be required to put `Plugin` behind an `Arc>`. Although plugins expose an asynchronous interface, the underlying Wasm engine can only execute a single function at a time. +Note that this plugin also holds an owned reference to the runtime, which is stored in the `Plugin` type. In asynchronous or multithreaded contexts, it may be required to put `Plugin` behind an `Arc>`. Although plugins expose an asynchronous interface, the underlying Wasm engine can only execute a single function at a time. > **Note**: This is a limitation of the WebAssembly standard itself. In the future, to work around this, we've been considering starting a pool of plugins, or instantiating a new plugin per call (this isn't as bad as it sounds, as instantiating a new plugin only takes about 30µs). @@ -203,7 +203,7 @@ To add a sync native function to a plugin, use the `.host_function` method: ```rust let builder = builder.host_function( - "add_f64", + "add_f64", |(a, b): (f64, f64)| a + b, ).unwrap(); ``` @@ -224,7 +224,7 @@ To add an async native function to a plugin, use the `.host_function_async` meth ```rust let builder = builder.host_function_async( - "half", + "half", |n: f64| async move { n / 2.0 }, ).unwrap(); ``` @@ -252,9 +252,9 @@ let plugin = builder .unwrap(); ``` -The `.init` method takes a single argument containing the plugin binary. +The `.init` method takes a single argument containing the plugin binary. -1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). +1. If not precompiled, use `PluginBinary::Wasm(bytes)`. This supports both the WebAssembly Textual format (`.wat`) and the WebAssembly Binary format (`.wasm`). 2. If precompiled, use `PluginBinary::Precompiled(bytes)`. This supports precompiled plugins ending in `.wasm.pre`. You need to be extra-careful when using precompiled plugins to ensure that the plugin target matches the target of the binary you are compiling. @@ -317,4 +317,4 @@ The `.call` method takes two arguments: This method is async, and must be `.await`ed. If something goes wrong (e.g. the plugin panics, or there is a type mismatch between the plugin and `WasiFn`), then this method will return an error. ## Last Notes -This has been a brief overview of how the plugin system currently works in Zed. We hope to implement higher-level affordances as time goes on, to make writing plugins easier, and providing tooling so that users of Zed may also write plugins to extend their own editors. \ No newline at end of file +This has been a brief overview of how the plugin system currently works in Zed. We hope to implement higher-level affordances as time goes on, to make writing plugins easier, and providing tooling so that users of Zed may also write plugins to extend their own editors. diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 05873818c7a5601f494ccc0cd87d3f5d25cf94cd..f9922bf5172a08fb2194816d4552a5776fb6c328 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -78,7 +78,7 @@ impl Rope { } pub fn slice_rows(&self, range: Range) -> Rope { - //This would be more efficient with a forward advance after the first, but it's fine + // This would be more efficient with a forward advance after the first, but it's fine. let start = self.point_to_offset(Point::new(range.start, 0)); let end = self.point_to_offset(Point::new(range.end, 0)); self.slice(start..end) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7dd4b85231695d8ece48dbd6075cd030c2081dfc..7ef21c42ed3886a81ad6b9b08a17f626d99acb1f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -423,7 +423,7 @@ impl ToolbarItemView for BufferSearchBar { } } -/// Registrar inverts the dependency between search and it's downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. +/// Registrar inverts the dependency between search and its downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. pub trait SearchActionsRegistrar { fn register_handler( &mut self, diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 81c4fbbc3d04b0d0bca6e4b70c074e3f75467999..801f02e600c1130eb5364e352f7f3823d6821f7a 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -1248,7 +1248,7 @@ impl SemanticIndex { impl Drop for JobHandle { fn drop(&mut self) { if let Some(inner) = Arc::get_mut(&mut self.tx) { - // This is the last instance of the JobHandle (regardless of it's origin - whether it was cloned or not) + // This is the last instance of the JobHandle (regardless of its origin - whether it was cloned or not) if let Some(tx) = inner.upgrade() { let mut tx = tx.lock(); *tx.borrow_mut() -= 1; diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index fa8112bac7d3cc0b33cab2d4cbfdf633cd9ee610..3a01f01ca89e03a35586ed325db69a93844ed270 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -983,7 +983,7 @@ impl Terminal { let mut terminal = if let Some(term) = term.try_lock_unfair() { term } else if self.last_synced.elapsed().as_secs_f32() > 0.25 { - term.lock_unfair() //It's been too long, force block + term.lock_unfair() // It's been too long, force block } else if let None = self.sync_task { //Skip this frame let delay = cx.background_executor().timer(Duration::from_millis(16)); @@ -1402,9 +1402,9 @@ fn content_index_for_mouse(pos: Point, size: &TerminalSize) -> usize { clamped_row * size.columns() + clamped_col } -///Converts an 8 bit ANSI color to it's GPUI equivalent. -///Accepts usize for compatibility with the alacritty::Colors interface, -///Other than that use case, should only be called with values in the [0,255] range +/// Converts an 8 bit ANSI color to it's GPUI equivalent. +/// Accepts `usize` for compatibility with the `alacritty::Colors` interface, +/// Other than that use case, should only be called with values in the [0,255] range pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla { let colors = theme.colors(); diff --git a/crates/ui/docs/hello-world.md b/crates/ui/docs/hello-world.md index 280763e27b114e56cbb27bb6859a15ec940d881b..12ee4d7abaaef55f0d7f5e674cdff97927a4182c 100644 --- a/crates/ui/docs/hello-world.md +++ b/crates/ui/docs/hello-world.md @@ -74,7 +74,7 @@ As you start using the Tailwind-style conventions you will be surprised how quic **Why `50.0/360.0` in `hsla()`?** -gpui [gpui::Hsla] use `0.0-1.0` for all it's values, but it is common for tools to use `0-360` for hue. +gpui [gpui::Hsla] use `0.0-1.0` for all its values, but it is common for tools to use `0-360` for hue. This may change in the future, but this is a little trick that let's you use familiar looking values. diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 08c95f2d939f0fc76be90d2186fab9a6788c8f8f..2180e0061773f8626292224e8fb3a2b97ab4cc65 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -73,7 +73,7 @@ impl RenderOnce for Checkbox { // - a previously agreed to license that has been updated // // For the sake of styles we treat the indeterminate state as selected, - // but it's icon will be different. + // but its icon will be different. let selected = self.checked == Selection::Selected || self.checked == Selection::Indeterminate; diff --git a/crates/ui/src/disableable.rs b/crates/ui/src/disableable.rs index ebd34fba1cf8289a830b56516e027c7eabaf8f12..9f08ed8d12dd1e0d468e40de85420f13dbbd1698 100644 --- a/crates/ui/src/disableable.rs +++ b/crates/ui/src/disableable.rs @@ -1,4 +1,4 @@ -/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing it's appearance to reflect that it is disabled. +/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing its appearance to reflect that it is disabled. pub trait Disableable { /// Sets whether the element is disabled. fn disabled(self, disabled: bool) -> Self; diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 613a79b19ed546283f3d54b45ea571becb3b9503..e32dd88b86fab6f0e8ad3fa9de1ce7ce6e8793a6 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -41,8 +41,8 @@ pub fn truncate(s: &str, max_chars: usize) -> &str { } } -/// Removes characters from the end of the string if it's length is greater than `max_chars` and -/// appends "..." to the string. Returns string unchanged if it's length is smaller than max_chars. +/// Removes characters from the end of the string if its length is greater than `max_chars` and +/// appends "..." to the string. Returns string unchanged if its length is smaller than max_chars. pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); @@ -53,8 +53,8 @@ pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { } } -/// Removes characters from the front of the string if it's length is greater than `max_chars` and -/// prepends the string with "...". Returns string unchanged if it's length is smaller than max_chars. +/// Removes characters from the front of the string if its length is greater than `max_chars` and +/// prepends the string with "...". Returns string unchanged if its length is smaller than max_chars. pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index cb30051ffa19f551b46d6ddefdbf981077cb9fed..7606e369d05cf02226ea5b783f4ff8369a661be3 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -40,7 +40,7 @@ Expect this to take 30min to an hour! Some of these steps will take quite a whil - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - Keep the token in the browser tab/editor for the next two steps 1. (Optional but reccomended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` -1. Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies: +1. Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: ``` cd .. git clone https://github.com/zed-industries/zed.dev From a5203364b1240c88aa464a13e87b15341d8f4d73 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 10 Jan 2024 10:35:06 -0500 Subject: [PATCH 076/334] Use the `.selected` style for buffer search option buttons (#4000) This PR updates the `IconButton`s used to control the buffer search options to use the `.selected` state to denote when they are active. This matches what we are doing in the project search. This should improve the contrast in certain themes. Release Notes: - Improved the active style for the search options in buffer search. --- crates/search/src/search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index 1b29801e03a8b370411be0d1def77c392cccb1dd..748996c389370bc576949e9c6752c26cb2e39f29 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -98,7 +98,7 @@ impl SearchOptions { IconButton::new(self.label(), self.icon()) .on_click(action) .style(ButtonStyle::Subtle) - .when(active, |button| button.style(ButtonStyle::Filled)) + .selected(active) .tooltip({ let action = self.to_toggle_action(); let label: SharedString = format!("Toggle {}", self.label()).into(); From f8e4fd012fec52879a2b7fd041c4d09e29ee694f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 09:26:15 -0700 Subject: [PATCH 077/334] collab 0.36.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f065290e1430befc01cf3b0ce4fed347bda0ee07..0a49aa72e2b3cb69c5fefe01836f86b7cd9e03a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.35.0" +version = "0.36.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 13bbd0cf2511d8b91015fd167842d82ed7b12df4..b0917104f98bc30d193e21171aca81f56199c83b 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.35.0" +version = "0.36.0" publish = false [[bin]] From 2923b71f83cfdc2d5168b683d784095b80ff27ba Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 Jan 2024 17:27:02 +0100 Subject: [PATCH 078/334] Replace `WindowContext::notify` with `WindowContext::refresh` --- crates/copilot/src/sign_in.rs | 2 +- crates/gpui/src/elements/div.rs | 24 ++++++++++---------- crates/gpui/src/elements/img.rs | 2 +- crates/gpui/src/elements/list.rs | 2 +- crates/gpui/src/elements/text.rs | 4 ++-- crates/gpui/src/view.rs | 1 + crates/gpui/src/window.rs | 20 +++++++++------- crates/terminal_view/src/terminal_element.rs | 2 +- crates/ui/src/components/popover_menu.rs | 2 +- crates/ui/src/components/right_click_menu.rs | 4 ++-- crates/workspace/src/pane_group.rs | 2 +- 11 files changed, 35 insertions(+), 30 deletions(-) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index ba5dbe0e315828ac1761cf1d76ac37bb8211ea4c..2ca31c7d332f7e5c6a32bf85b93d79dd2ee714d9 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -109,7 +109,7 @@ impl CopilotCodeVerification { let user_code = data.user_code.clone(); move |_, cx| { cx.write_to_clipboard(ClipboardItem::new(user_code.clone())); - cx.notify(); + cx.refresh(); } }) .child(Label::new(data.user_code.clone())) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 45097411d13875c7b1a8fecc4888711dbc0d9dd3..d750d692f0d54c18f4e10ae92693920cec1825f6 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1027,7 +1027,7 @@ impl Interactivity { if e.modifiers.command != command_held && text_bounds.contains(&cx.mouse_position()) { - cx.notify(); + cx.refresh(); } } }); @@ -1038,7 +1038,7 @@ impl Interactivity { if phase == DispatchPhase::Capture && bounds.contains(&event.position) != hovered { - cx.notify(); + cx.refresh(); } }, ); @@ -1188,7 +1188,7 @@ impl Interactivity { if phase == DispatchPhase::Capture && group_bounds.contains(&event.position) != hovered { - cx.notify(); + cx.refresh(); } }); } @@ -1203,7 +1203,7 @@ impl Interactivity { if phase == DispatchPhase::Capture && bounds.contains(&event.position) != hovered { - cx.notify(); + cx.refresh(); } }); } @@ -1237,7 +1237,7 @@ impl Interactivity { if can_drop { listener(drag.value.as_ref(), cx); - cx.notify(); + cx.refresh(); cx.stop_propagation(); } } @@ -1268,7 +1268,7 @@ impl Interactivity { && interactive_bounds.visibly_contains(&event.position, cx) { *pending_mouse_down.borrow_mut() = Some(event.clone()); - cx.notify(); + cx.refresh(); } } }); @@ -1299,7 +1299,7 @@ impl Interactivity { cursor_offset, }); pending_mouse_down.take(); - cx.notify(); + cx.refresh(); cx.stop_propagation(); } } @@ -1319,7 +1319,7 @@ impl Interactivity { pending_mouse_down.borrow_mut(); if pending_mouse_down.is_some() { captured_mouse_down = pending_mouse_down.take(); - cx.notify(); + cx.refresh(); } } // Fire click handlers during the bubble phase. @@ -1413,7 +1413,7 @@ impl Interactivity { _task: None, }, ); - cx.notify(); + cx.refresh(); }) .ok(); } @@ -1453,7 +1453,7 @@ impl Interactivity { cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { if phase == DispatchPhase::Capture { *active_state.borrow_mut() = ElementClickedState::default(); - cx.notify(); + cx.refresh(); } }); } else { @@ -1471,7 +1471,7 @@ impl Interactivity { if group || element { *active_state.borrow_mut() = ElementClickedState { group, element }; - cx.notify(); + cx.refresh(); } } }); @@ -1531,7 +1531,7 @@ impl Interactivity { } if *scroll_offset != old_scroll_offset { - cx.notify(); + cx.refresh(); cx.stop_propagation(); } } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 650b5b666bc821e15873c53f2cdc56c0546b6a20..123bfed42a38642b1789675ffa00764418169be9 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -109,7 +109,7 @@ impl Element for Img { } else { cx.spawn(|mut cx| async move { if image_future.await.ok().is_some() { - cx.on_next_frame(|cx| cx.notify()); + cx.on_next_frame(|cx| cx.refresh()); } }) .detach(); diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 2a47a16741cf67c0cefb8a094d2f9e506cacbdf4..9081ceadca8440e6ac7d631b460d1b4b277ff932 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -258,7 +258,7 @@ impl StateInner { ); } - cx.notify(); + cx.refresh(); } fn logical_scroll_top(&self) -> ListOffset { diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 29c93fd19e91518a96a5f663352b92519264aca9..ec74eb2987db9e4234f15af6b13c926226b61ca9 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -389,7 +389,7 @@ impl Element for InteractiveText { } mouse_down.take(); - cx.notify(); + cx.refresh(); } }); } else { @@ -399,7 +399,7 @@ impl Element for InteractiveText { text_state.index_for_position(bounds, event.position) { mouse_down.set(Some(mouse_down_index)); - cx.notify(); + cx.refresh(); } } }); diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 6ec78e0d2e8bd62dc5e30d7a47ba77cd254cb892..22443a939546d9c86a534d62407ca18d090e3db4 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -290,6 +290,7 @@ impl Element for AnyView { && cache_key.stacking_order == *cx.stacking_order() && cache_key.text_style == cx.text_style() && !cx.window.dirty_views.contains(&self.entity_id()) + && !cx.window.refreshing { cx.reuse_geometry(); return; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 376f4c24662f39a766c9aaa3ce3b2c69a4f75f9c..fc60f9c5c317ad81a16d64cfd03232781676ca22 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -269,6 +269,7 @@ pub struct Window { bounds_observers: SubscriberSet<(), AnyObserver>, active: bool, pub(crate) dirty: bool, + pub(crate) refreshing: bool, pub(crate) drawing: bool, activation_observers: SubscriberSet<(), AnyObserver>, pub(crate) focus: Option, @@ -424,6 +425,7 @@ impl Window { bounds_observers: SubscriberSet::new(), active: false, dirty: false, + refreshing: false, drawing: false, activation_observers: SubscriberSet::new(), focus: None, @@ -478,8 +480,9 @@ impl<'a> WindowContext<'a> { } /// Mark the window as dirty, scheduling it to be redrawn on the next frame. - pub fn notify(&mut self) { + pub fn refresh(&mut self) { if !self.window.drawing { + self.window.refreshing = true; self.window.dirty = true; } } @@ -519,7 +522,7 @@ impl<'a> WindowContext<'a> { self.window.focus_invalidated = true; } - self.notify(); + self.refresh(); } /// Remove focus from all elements within this context's window. @@ -529,7 +532,7 @@ impl<'a> WindowContext<'a> { } self.window.focus = None; - self.notify(); + self.refresh(); } pub fn disable_focus(&mut self) { @@ -795,7 +798,7 @@ impl<'a> WindowContext<'a> { self.window.viewport_size = self.window.platform_window.content_size(); self.window.bounds = self.window.platform_window.bounds(); self.window.display_id = self.window.platform_window.display().id(); - self.notify(); + self.refresh(); self.window .bounds_observers @@ -1499,6 +1502,7 @@ impl<'a> WindowContext<'a> { self.platform.set_cursor_style(cursor_style); } + self.window.refreshing = false; self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); @@ -1641,12 +1645,12 @@ impl<'a> WindowContext<'a> { if event.is::() { // If this was a mouse move event, redraw the window so that the // active drag can follow the mouse cursor. - self.notify(); + self.refresh(); } else if event.is::() { // If this was a mouse up event, cancel the active drag and redraw // the window. self.active_drag = None; - self.notify(); + self.refresh(); } } } @@ -2169,7 +2173,7 @@ impl VisualContext for WindowContext<'_> { { let view = self.new_view(build_view); self.window.root_view = Some(view.clone().into()); - self.notify(); + self.refresh(); view } @@ -2583,7 +2587,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } if !self.window.drawing { - self.window_cx.notify(); + self.window_cx.window.dirty = true; self.window_cx.app.push_effect(Effect::Notify { emitter: self.view.model.entity_id, }); diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index d936716032a53b432d2f6f1a5dc6b79069656c8b..9cc55f0e7451698b3cfcc292cb0c8929ff2b489e 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -584,7 +584,7 @@ impl TerminalElement { this.update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); if handled { - cx.notify(); + cx.refresh(); } } }); diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index fb823b05dba3d18cea7992948abb50f827167e1c..bc27e5eea2f1ee4e01db197395ab9aeccdf3fb27 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -55,7 +55,7 @@ impl PopoverMenu { } } *menu2.borrow_mut() = None; - cx.notify(); + cx.refresh(); }) .detach(); cx.focus_view(&new_menu); diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 8bf40f61a8264ec3b00eec3ec844efa247876da7..56590cdde89f99a8f51f8bb4b10ddee7a99fc7bd 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -153,7 +153,7 @@ impl Element for RightClickMenu { } } *menu2.borrow_mut() = None; - cx.notify(); + cx.refresh(); }) .detach(); cx.focus_view(&new_menu); @@ -166,7 +166,7 @@ impl Element for RightClickMenu { } else { cx.mouse_position() }; - cx.notify(); + cx.refresh(); } }); } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 236daf60f8a747f4d43f4bc0a73109cd16e2ad3e..9ce191bc94644c3fa714d8b4c696aa0c7ae5f0e5 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -698,7 +698,7 @@ mod element { // todo!(schedule serialize) // workspace.schedule_serialize(cx); - cx.notify(); + cx.refresh(); } fn push_handle( From 1c1151a0ed2f26ea2cf637aa4d07567ab6b4f372 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 09:29:44 -0700 Subject: [PATCH 079/334] Remove ChannelsAlpha flag Welcome to the party! --- crates/collab_ui/src/chat_panel.rs | 9 +- crates/collab_ui/src/collab_panel.rs | 196 +++++++++++----------- crates/collab_ui/src/collab_ui.rs | 5 - crates/feature_flags/src/feature_flags.rs | 6 - 4 files changed, 98 insertions(+), 118 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 5786ab10d4ca59b998b1b16ea7bb3c53611b4399..d52f285c898549d082a90db37a962698f27a577a 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; +use crate::{channel_view::ChannelView, ChatPanelSettings}; use anyhow::Result; use call::ActiveCall; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -612,9 +612,6 @@ impl Panel for ChatPanel { self.active = active; if active { self.acknowledge_last_message(cx); - if !is_channels_feature_enabled(cx) { - cx.emit(Event::Dismissed); - } } } @@ -623,10 +620,6 @@ impl Panel for ChatPanel { } fn icon(&self, cx: &WindowContext) -> Option { - if !is_channels_feature_enabled(cx) { - return None; - } - Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5ad3d6cfa3213119551ed341be33816336f8ca5c..aa348ea9d504a46d4477029f43c6627c16fbe7d9 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -12,7 +12,6 @@ use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; -use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext, @@ -278,10 +277,6 @@ impl CollabPanel { })); this.subscriptions .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx))); - this.subscriptions - .push(cx.observe_flag::(move |_, this, cx| { - this.update_entries(true, cx) - })); this.subscriptions.push(cx.subscribe( &this.channel_store, |this, _channel_store, e, cx| match e { @@ -517,115 +512,118 @@ impl CollabPanel { let mut request_entries = Vec::new(); - if cx.has_flag::() { - self.entries.push(ListEntry::Header(Section::Channels)); + self.entries.push(ListEntry::Header(Section::Channels)); - if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_store.ordered_channels().enumerate().map( - |(ix, (_, channel))| StringMatchCandidate { + if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { + self.match_candidates.clear(); + self.match_candidates + .extend( + channel_store + .ordered_channels() + .enumerate() + .map(|(ix, (_, channel))| StringMatchCandidate { id: ix, string: channel.name.clone().into(), char_bag: channel.name.chars().collect(), - }, - )); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - if let Some(state) = &self.channel_editing_state { - if matches!(state, ChannelEditingState::Create { location: None, .. }) { - self.entries.push(ListEntry::ChannelEditor { depth: 0 }); - } + }), + ); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + if let Some(state) = &self.channel_editing_state { + if matches!(state, ChannelEditingState::Create { location: None, .. }) { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } - let mut collapse_depth = None; - for mat in matches { - let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); - let depth = channel.parent_path.len(); - - if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + } + let mut collapse_depth = None; + for mat in matches { + let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); + let depth = channel.parent_path.len(); + + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { collapse_depth = Some(depth); - } else if let Some(collapsed_depth) = collapse_depth { - if depth > collapsed_depth { - continue; - } - if self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else { - collapse_depth = None; - } + } else { + collapse_depth = None; } + } - let has_children = channel_store - .channel_at_index(mat.candidate_id + 1) - .map_or(false, |next_channel| { - next_channel.parent_path.ends_with(&[channel.id]) - }); + let has_children = channel_store + .channel_at_index(mat.candidate_id + 1) + .map_or(false, |next_channel| { + next_channel.parent_path.ends_with(&[channel.id]) + }); - match &self.channel_editing_state { - Some(ChannelEditingState::Create { - location: parent_id, - .. - }) if *parent_id == Some(channel.id) => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children: false, - }); - self.entries - .push(ListEntry::ChannelEditor { depth: depth + 1 }); - } - Some(ChannelEditingState::Rename { - location: parent_id, - .. - }) if parent_id == &channel.id => { - self.entries.push(ListEntry::ChannelEditor { depth }); - } - _ => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children, - }); - } + match &self.channel_editing_state { + Some(ChannelEditingState::Create { + location: parent_id, + .. + }) if *parent_id == Some(channel.id) => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children: false, + }); + self.entries + .push(ListEntry::ChannelEditor { depth: depth + 1 }); + } + Some(ChannelEditingState::Rename { + location: parent_id, + .. + }) if parent_id == &channel.id => { + self.entries.push(ListEntry::ChannelEditor { depth }); + } + _ => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children, + }); } } } + } - let channel_invites = channel_store.channel_invitations(); - if !channel_invites.is_empty() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { - StringMatchCandidate { - id: ix, - string: channel.name.clone().into(), - char_bag: channel.name.chars().collect(), - } - })); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - request_entries.extend(matches.iter().map(|mat| { - ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) + let channel_invites = channel_store.channel_invitations(); + if !channel_invites.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { + StringMatchCandidate { + id: ix, + string: channel.name.clone().into(), + char_bag: channel.name.chars().collect(), + } })); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend( + matches + .iter() + .map(|mat| ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())), + ); - if !request_entries.is_empty() { - self.entries - .push(ListEntry::Header(Section::ChannelInvites)); - if !self.collapsed_sections.contains(&Section::ChannelInvites) { - self.entries.append(&mut request_entries); - } + if !request_entries.is_empty() { + self.entries + .push(ListEntry::Header(Section::ChannelInvites)); + if !self.collapsed_sections.contains(&Section::ChannelInvites) { + self.entries.append(&mut request_entries); } } } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 3c0473e67d0a687308c00097c554fc87f47645c1..455ae64ef1d35a9f1ec0896ac2cb2367fdf47d3a 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -12,7 +12,6 @@ use std::{rc::Rc, sync::Arc}; use call::{report_call_event_for_room, ActiveCall, Room}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; -use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use gpui::{ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds, WindowKind, WindowOptions, @@ -159,7 +158,3 @@ fn notification_window_options( // .with_style(container) // .into_any() // } - -fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { - cx.is_staff() || cx.has_flag::() -} diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index 065d06f96d1b1765828329464ddb149da371d63b..ea16ff3f7291fd838be45fd826e7c7504ba914fd 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -16,12 +16,6 @@ pub trait FeatureFlag { const NAME: &'static str; } -pub enum ChannelsAlpha {} - -impl FeatureFlag for ChannelsAlpha { - const NAME: &'static str = "channels_alpha"; -} - pub trait FeatureFlagViewExt { fn observe_flag(&mut self, callback: F) -> Subscription where From 282184a673514c66c274290e2b7f16460a87fc12 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 10 Jan 2024 18:11:05 +0100 Subject: [PATCH 080/334] editor: Use inclusive ranges for git diff resolution. (#3999) The culprit was in display map which was resolving next valid point for the editor, without regard for whether that point belongs to the same excerpt. We now make an end point a minimum of the end point passed in and the start of excerpt header, if there are any. This bug existed in Zed1 as well. Fixes: Diff markers in multibuffer search overlap with dividers between excepts (shouldn't extend all the way into the divider region) Release Notes: - Fixed diff markers being drawn incorrectly near headers in multibuffer views. --- crates/editor/src/element.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fc3c53f34343684ed5d39305d1c30d4fe7872881..7efb43bd4852fc7ef1d8e5a5a43a79ad72a0303e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -804,9 +804,22 @@ impl EditorElement { let start_row = display_row_range.start; let end_row = display_row_range.end; + // If we're in a multibuffer, row range span might include an + // excerpt header, so if we were to draw the marker straight away, + // the hunk might include the rows of that header. + // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap. + // Instead, we simply check whether the range we're dealing with includes + // any custom elements and if so, we stop painting the diff hunk on the first row of that custom element. + let end_row_in_current_excerpt = layout + .position_map + .snapshot + .blocks_in_range(start_row..end_row) + .next() + .map(|(start_row, _)| start_row) + .unwrap_or(end_row); let start_y = start_row as f32 * line_height - scroll_top; - let end_y = end_row as f32 * line_height - scroll_top; + let end_y = end_row_in_current_excerpt as f32 * line_height - scroll_top; let width = 0.275 * line_height; let highlight_origin = bounds.origin + point(-width, start_y); From 8d1bca450faa7fcbac4779b391258d8520c4b58f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 10:18:45 -0700 Subject: [PATCH 081/334] Remove extra assertion As part of debugging the port of following tests we added an assertion that the project was dropped. Now that we initialize the editor and handle focus correctly in tests, the project is retained by `refresh_document_highlights`. That doesn't affect the meaning of the tests --- crates/collab/src/tests/following_tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 8f6433c66809407c63989f2e8a865c1644ba35ab..9209760353935bfdd3573317a9cd3d0ca4e85573 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -161,7 +161,6 @@ async fn test_basic_following( .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) .await .unwrap(); - let weak_project_c = project_c.downgrade(); drop(project_c); // Client C also follows client A. @@ -248,7 +247,6 @@ async fn test_basic_following( cx_c.cx.update(|_| {}); weak_workspace_c.assert_dropped(); - weak_project_c.assert_dropped(); // Clients A and B see that client B is following A, and client C is not present in the followers. executor.run_until_parked(); From 69a93edabdafc6c97550b2368b1f085e142a5e1e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 10:36:08 -0800 Subject: [PATCH 082/334] Ensure `ArenaRef` pointers are aligned to their contained type Co-Authored-By: Antonio Scandurra --- crates/gpui/src/arena.rs | 50 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/arena.rs b/crates/gpui/src/arena.rs index bb493a6d06487a5b07f7a2afbedc36d85fa9f715..b3d7f9b0ecf530a2dbe2c8f9d4dd286ac971a2d9 100644 --- a/crates/gpui/src/arena.rs +++ b/crates/gpui/src/arena.rs @@ -66,18 +66,19 @@ impl Arena { } unsafe { - let layout = alloc::Layout::new::().pad_to_align(); - let next_offset = self.offset.add(layout.size()); - assert!(next_offset <= self.end); + let layout = alloc::Layout::new::(); + let offset = self.offset.add(self.offset.align_offset(layout.align())); + let next_offset = offset.add(layout.size()); + assert!(next_offset <= self.end, "not enough space in Arena"); let result = ArenaBox { - ptr: self.offset.cast(), + ptr: offset.cast(), valid: self.valid.clone(), }; inner_writer(result.ptr, f); self.elements.push(ArenaElement { - value: self.offset, + value: offset, drop: drop::, }); self.offset = next_offset; @@ -199,4 +200,43 @@ mod tests { arena.clear(); assert!(dropped.get()); } + + #[test] + #[should_panic(expected = "not enough space in Arena")] + fn test_arena_overflow() { + let mut arena = Arena::new(16); + arena.alloc(|| 1u64); + arena.alloc(|| 2u64); + // This should panic. + arena.alloc(|| 3u64); + } + + #[test] + fn test_arena_alignment() { + let mut arena = Arena::new(256); + let x1 = arena.alloc(|| 1u8); + let x2 = arena.alloc(|| 2u16); + let x3 = arena.alloc(|| 3u32); + let x4 = arena.alloc(|| 4u64); + let x5 = arena.alloc(|| 5u64); + + assert_eq!(*x1, 1); + assert_eq!(*x2, 2); + assert_eq!(*x3, 3); + assert_eq!(*x4, 4); + assert_eq!(*x5, 5); + + assert_eq!(x1.ptr.align_offset(std::mem::align_of_val(&*x1)), 0); + assert_eq!(x2.ptr.align_offset(std::mem::align_of_val(&*x2)), 0); + } + + #[test] + #[should_panic(expected = "attempted to dereference an ArenaRef after its Arena was cleared")] + fn test_arena_use_after_clear() { + let mut arena = Arena::new(16); + let value = arena.alloc(|| 1u64); + + arena.clear(); + let _read_value = *value; + } } From 61a9a3a274698f81561cb3ee0420848baeb55608 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 11:52:33 -0700 Subject: [PATCH 083/334] Revert "Remove ChannelsAlpha flag" This reverts commit 1c1151a0ed2f26ea2cf637aa4d07567ab6b4f372. --- crates/collab_ui/src/chat_panel.rs | 9 +- crates/collab_ui/src/collab_panel.rs | 196 +++++++++++----------- crates/collab_ui/src/collab_ui.rs | 5 + crates/feature_flags/src/feature_flags.rs | 6 + 4 files changed, 118 insertions(+), 98 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index d52f285c898549d082a90db37a962698f27a577a..5786ab10d4ca59b998b1b16ea7bb3c53611b4399 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{channel_view::ChannelView, ChatPanelSettings}; +use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; use anyhow::Result; use call::ActiveCall; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -612,6 +612,9 @@ impl Panel for ChatPanel { self.active = active; if active { self.acknowledge_last_message(cx); + if !is_channels_feature_enabled(cx) { + cx.emit(Event::Dismissed); + } } } @@ -620,6 +623,10 @@ impl Panel for ChatPanel { } fn icon(&self, cx: &WindowContext) -> Option { + if !is_channels_feature_enabled(cx) { + return None; + } + Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index aa348ea9d504a46d4477029f43c6627c16fbe7d9..5ad3d6cfa3213119551ed341be33816336f8ca5c 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -12,6 +12,7 @@ use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; +use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext, @@ -277,6 +278,10 @@ impl CollabPanel { })); this.subscriptions .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx))); + this.subscriptions + .push(cx.observe_flag::(move |_, this, cx| { + this.update_entries(true, cx) + })); this.subscriptions.push(cx.subscribe( &this.channel_store, |this, _channel_store, e, cx| match e { @@ -512,118 +517,115 @@ impl CollabPanel { let mut request_entries = Vec::new(); - self.entries.push(ListEntry::Header(Section::Channels)); + if cx.has_flag::() { + self.entries.push(ListEntry::Header(Section::Channels)); - if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { - self.match_candidates.clear(); - self.match_candidates - .extend( - channel_store - .ordered_channels() - .enumerate() - .map(|(ix, (_, channel))| StringMatchCandidate { + if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_store.ordered_channels().enumerate().map( + |(ix, (_, channel))| StringMatchCandidate { id: ix, string: channel.name.clone().into(), char_bag: channel.name.chars().collect(), - }), - ); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - if let Some(state) = &self.channel_editing_state { - if matches!(state, ChannelEditingState::Create { location: None, .. }) { - self.entries.push(ListEntry::ChannelEditor { depth: 0 }); - } - } - let mut collapse_depth = None; - for mat in matches { - let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); - let depth = channel.parent_path.len(); - - if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else if let Some(collapsed_depth) = collapse_depth { - if depth > collapsed_depth { - continue; - } - if self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else { - collapse_depth = None; + }, + )); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + if let Some(state) = &self.channel_editing_state { + if matches!(state, ChannelEditingState::Create { location: None, .. }) { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } } + let mut collapse_depth = None; + for mat in matches { + let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); + let depth = channel.parent_path.len(); - let has_children = channel_store - .channel_at_index(mat.candidate_id + 1) - .map_or(false, |next_channel| { - next_channel.parent_path.ends_with(&[channel.id]) - }); - - match &self.channel_editing_state { - Some(ChannelEditingState::Create { - location: parent_id, - .. - }) if *parent_id == Some(channel.id) => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children: false, - }); - self.entries - .push(ListEntry::ChannelEditor { depth: depth + 1 }); - } - Some(ChannelEditingState::Rename { - location: parent_id, - .. - }) if parent_id == &channel.id => { - self.entries.push(ListEntry::ChannelEditor { depth }); + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else { + collapse_depth = None; + } } - _ => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children, + + let has_children = channel_store + .channel_at_index(mat.candidate_id + 1) + .map_or(false, |next_channel| { + next_channel.parent_path.ends_with(&[channel.id]) }); + + match &self.channel_editing_state { + Some(ChannelEditingState::Create { + location: parent_id, + .. + }) if *parent_id == Some(channel.id) => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children: false, + }); + self.entries + .push(ListEntry::ChannelEditor { depth: depth + 1 }); + } + Some(ChannelEditingState::Rename { + location: parent_id, + .. + }) if parent_id == &channel.id => { + self.entries.push(ListEntry::ChannelEditor { depth }); + } + _ => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children, + }); + } } } } - } - let channel_invites = channel_store.channel_invitations(); - if !channel_invites.is_empty() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { - StringMatchCandidate { - id: ix, - string: channel.name.clone().into(), - char_bag: channel.name.chars().collect(), - } + let channel_invites = channel_store.channel_invitations(); + if !channel_invites.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { + StringMatchCandidate { + id: ix, + string: channel.name.clone().into(), + char_bag: channel.name.chars().collect(), + } + })); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend(matches.iter().map(|mat| { + ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) })); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - request_entries.extend( - matches - .iter() - .map(|mat| ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())), - ); - if !request_entries.is_empty() { - self.entries - .push(ListEntry::Header(Section::ChannelInvites)); - if !self.collapsed_sections.contains(&Section::ChannelInvites) { - self.entries.append(&mut request_entries); + if !request_entries.is_empty() { + self.entries + .push(ListEntry::Header(Section::ChannelInvites)); + if !self.collapsed_sections.contains(&Section::ChannelInvites) { + self.entries.append(&mut request_entries); + } } } } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 455ae64ef1d35a9f1ec0896ac2cb2367fdf47d3a..3c0473e67d0a687308c00097c554fc87f47645c1 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -12,6 +12,7 @@ use std::{rc::Rc, sync::Arc}; use call::{report_call_event_for_room, ActiveCall, Room}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; +use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use gpui::{ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds, WindowKind, WindowOptions, @@ -158,3 +159,7 @@ fn notification_window_options( // .with_style(container) // .into_any() // } + +fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { + cx.is_staff() || cx.has_flag::() +} diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index ea16ff3f7291fd838be45fd826e7c7504ba914fd..065d06f96d1b1765828329464ddb149da371d63b 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -16,6 +16,12 @@ pub trait FeatureFlag { const NAME: &'static str; } +pub enum ChannelsAlpha {} + +impl FeatureFlag for ChannelsAlpha { + const NAME: &'static str = "channels_alpha"; +} + pub trait FeatureFlagViewExt { fn observe_flag(&mut self, callback: F) -> Subscription where From c98d7adf83f0c067c673f4cea0cded958ad51c09 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 9 Jan 2024 22:22:59 -0800 Subject: [PATCH 084/334] Audit all TODOs in Zed and mark port related todos --- crates/call/src/call.rs | 3 ++- crates/editor/src/element.rs | 1 - crates/file_finder/src/file_finder.rs | 2 +- crates/gpui/src/action.rs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 3561cc33852a84d78ed21371432743d9dc540862..fb437162f2350c9fac054e52cc205b43428b1381 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -239,7 +239,8 @@ impl ActiveCall { if result.is_ok() { this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?; } else { - // TODO: Resport collaboration error + //TODO: report collaboration error + log::error!("invite failed: {:?}", result); } this.update(&mut cx, |this, cx| { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7efb43bd4852fc7ef1d8e5a5a43a79ad72a0303e..ca7b193a45f27fffa2cec27eaac562839db1cfbc 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1010,7 +1010,6 @@ impl EditorElement { .chars_at(cursor_position) .next() .and_then(|(character, _)| { - // todo!() currently shape_line panics if text conatins newlines let text = if character == '\n' { SharedString::from(" ") } else { diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index d49eb9ee603d7e819bab5097adac874107fd9a3f..0fe36084c2abda205fc873ef46b7eb7afd062491 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -312,7 +312,7 @@ impl FileFinderDelegate { cx: &mut ViewContext, ) -> Self { cx.observe(&project, |file_finder, _, cx| { - //todo!() We should probably not re-render on every project anything + //todo We should probably not re-render on every project anything file_finder .picker .update(cx, |picker, cx| picker.refresh(cx)) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index ef02316f83ea9dfa57c68106f6b5706b755e3cd6..b9cdd4a8bce81ebb4c26178b40a665720985343e 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -128,6 +128,7 @@ impl ActionRegistry { } fn insert_action(&mut self, action: ActionData) { + //todo!(remove) let name: SharedString = action.name.into(); self.builders_by_name.insert(name.clone(), action.build); self.names_by_type_id.insert(action.type_id, name.clone()); From 2a09c6aad5b67be79d4f016416057f5e106c5fbf Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 10:23:17 -0800 Subject: [PATCH 085/334] Restore missing test --- .../collab/src/tests/channel_message_tests.rs | 266 +++++++++--------- 1 file changed, 134 insertions(+), 132 deletions(-) diff --git a/crates/collab/src/tests/channel_message_tests.rs b/crates/collab/src/tests/channel_message_tests.rs index 5870bd193842620a2953e577ab0005b48237bcde..e59aa3c705fd7b19c77b0a3c231fed5486a72786 100644 --- a/crates/collab/src/tests/channel_message_tests.rs +++ b/crates/collab/src/tests/channel_message_tests.rs @@ -1,7 +1,9 @@ use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; use channel::{ChannelChat, ChannelMessageId, MessageParams}; +use collab_ui::chat_panel::ChatPanel; use gpui::{BackgroundExecutor, Model, TestAppContext}; use rpc::Notification; +use workspace::dock::Panel; #[gpui::test] async fn test_basic_channel_messages( @@ -273,135 +275,135 @@ fn assert_messages(chat: &Model, messages: &[&str], cx: &mut TestAp ); } -//todo!(collab_ui) -// #[gpui::test] -// async fn test_channel_message_changes( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; - -// let channel_id = server -// .make_channel( -// "the-channel", -// None, -// (&client_a, cx_a), -// &mut [(&client_b, cx_b)], -// ) -// .await; - -// // Client A sends a message, client B should see that there is a new message. -// let channel_chat_a = client_a -// .channel_store() -// .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx)) -// .await -// .unwrap(); - -// channel_chat_a -// .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap()) -// .await -// .unwrap(); - -// executor.run_until_parked(); - -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); - -// assert!(b_has_messages); - -// // Opening the chat should clear the changed flag. -// cx_b.update(|cx| { -// collab_ui::init(&client_b.app_state, cx); -// }); -// let project_b = client_b.build_empty_local_project(cx_b); -// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b); -// let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx)); -// chat_panel_b -// .update(cx_b, |chat_panel, cx| { -// chat_panel.set_active(true, cx); -// chat_panel.select_channel(channel_id, None, cx) -// }) -// .await -// .unwrap(); - -// executor.run_until_parked(); - -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); - -// assert!(!b_has_messages); - -// // Sending a message while the chat is open should not change the flag. -// channel_chat_a -// .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap()) -// .await -// .unwrap(); - -// executor.run_until_parked(); - -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); - -// assert!(!b_has_messages); - -// // Sending a message while the chat is closed should change the flag. -// chat_panel_b.update(cx_b, |chat_panel, cx| { -// chat_panel.set_active(false, cx); -// }); - -// // Sending a message while the chat is open should not change the flag. -// channel_chat_a -// .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap()) -// .await -// .unwrap(); - -// executor.run_until_parked(); - -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); - -// assert!(b_has_messages); - -// // Closing the chat should re-enable change tracking -// cx_b.update(|_| drop(chat_panel_b)); - -// channel_chat_a -// .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap()) -// .await -// .unwrap(); - -// executor.run_until_parked(); - -// let b_has_messages = cx_b.read_with(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_new_messages(channel_id) -// .unwrap() -// }); - -// assert!(b_has_messages); -// } +#[gpui::test] +async fn test_channel_message_changes( + executor: BackgroundExecutor, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(executor.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + + let channel_id = server + .make_channel( + "the-channel", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b)], + ) + .await; + + // Client A sends a message, client B should see that there is a new message. + let channel_chat_a = client_a + .channel_store() + .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx)) + .await + .unwrap(); + + channel_chat_a + .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap()) + .await + .unwrap(); + + executor.run_until_parked(); + + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); + + assert!(b_has_messages); + + // Opening the chat should clear the changed flag. + cx_b.update(|cx| { + collab_ui::init(&client_b.app_state, cx); + }); + let project_b = client_b.build_empty_local_project(cx_b); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + + let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx)); + chat_panel_b + .update(cx_b, |chat_panel, cx| { + chat_panel.set_active(true, cx); + chat_panel.select_channel(channel_id, None, cx) + }) + .await + .unwrap(); + + executor.run_until_parked(); + + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); + + assert!(!b_has_messages); + + // Sending a message while the chat is open should not change the flag. + channel_chat_a + .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap()) + .await + .unwrap(); + + executor.run_until_parked(); + + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); + + assert!(!b_has_messages); + + // Sending a message while the chat is closed should change the flag. + chat_panel_b.update(cx_b, |chat_panel, cx| { + chat_panel.set_active(false, cx); + }); + + // Sending a message while the chat is open should not change the flag. + channel_chat_a + .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap()) + .await + .unwrap(); + + executor.run_until_parked(); + + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); + + assert!(b_has_messages); + + // Closing the chat should re-enable change tracking + cx_b.update(|_| drop(chat_panel_b)); + + channel_chat_a + .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap()) + .await + .unwrap(); + + executor.run_until_parked(); + + let b_has_messages = cx_b.update(|cx| { + client_b + .channel_store() + .read(cx) + .has_new_messages(channel_id) + .unwrap() + }); + + assert!(b_has_messages); +} From 7ef88397c9a7a8600712af0f142431dd4568c2f2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 11:23:18 -0800 Subject: [PATCH 086/334] Fix seg fault when using the WindowContext::on_window_should_close() API --- crates/collab_ui/src/collab_ui.rs | 1 - crates/editor/src/display_map/wrap_map.rs | 2 +- crates/gpui/src/platform/mac/window.rs | 4 +--- crates/gpui/src/window.rs | 11 ++++++++++- crates/zed/src/zed.rs | 1 + 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 3c0473e67d0a687308c00097c554fc87f47645c1..c8230620b4c7756dbfee2b26011c32634f3b005a 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -111,7 +111,6 @@ fn notification_window_options( let screen_bounds = screen.bounds(); let size: Size = window_size.into(); - // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument. let bounds = gpui::Bounds:: { origin: screen_bounds.upper_right() - point( diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 05aa3816271eef0f1286f7f2fe22367864846888..dbd58b0accd89c4054d266f4867659add671a03b 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1043,7 +1043,7 @@ mod tests { #[gpui::test(iterations = 100)] async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) { - // todo!() this test is flaky + // todo this test is flaky init_test(cx); cx.background_executor.set_block_on_ticks(0..=50); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 6d03a3b5cd698c62147bd75b62c3594309f38797..d2ce87f5fa43b8d5ea7011abd0ee7e42bd8dfa77 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -269,6 +269,7 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C sel!(windowShouldClose:), window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, ); + decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel)); decl.add_method( @@ -685,9 +686,6 @@ impl Drop for MacWindow { this.executor .spawn(async move { unsafe { - // todo!() this panic()s when you click the red close button - // unless should_close returns false. - // (luckliy in zed it always returns false) window.close(); } }) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 187f28c14b460c08e20500bed19538810f5d814a..10c8651924118fe7f5050e3f50e21bf4ef54f274 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1904,7 +1904,16 @@ impl<'a> WindowContext<'a> { let mut this = self.to_async(); self.window .platform_window - .on_should_close(Box::new(move || this.update(|_, cx| f(cx)).unwrap_or(true))) + .on_should_close(Box::new(move || { + this.update(|_, cx| { + // Ensure that the window is removed from the app if it's been closed. + if f(cx) { + cx.remove_window(); + } + false + }) + .unwrap_or(true) + })) } } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 38e0bec14e30418bee589450bc21cccc31364daf..c2725eef64029a11cbf449769ee5f025ae7b0535 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -148,6 +148,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { cx.on_window_should_close(move |cx| { handle .update(cx, |workspace, cx| { + // We'll handle closing asynchoronously workspace.close_window(&Default::default(), cx); false }) From 95537598995e7d5e0ca22f6ac5c38fa521542a9d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:02:59 +0100 Subject: [PATCH 087/334] Remove todo from search tests --- crates/search/src/buffer_search.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7ef21c42ed3886a81ad6b9b08a17f626d99acb1f..f7e36fe696258fa1a361bc6bd212f1d888efc2f8 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1130,7 +1130,6 @@ mod tests { #[gpui::test] async fn test_search_simple(cx: &mut TestAppContext) { let (editor, search_bar, cx) = init_test(cx); - // todo! osiewicz: these tests asserted on background color as well, that should be brought back. let display_points_of = |background_highlights: Vec<(Range, Hsla)>| { background_highlights .into_iter() @@ -1395,7 +1394,6 @@ mod tests { }) .await .unwrap(); - // todo! osiewicz: these tests previously asserted on background color highlights; that should be introduced back. let display_points_of = |background_highlights: Vec<(Range, Hsla)>| { background_highlights .into_iter() From 4bcac68c8cdd809209bb830d1af04a1780dcc217 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 11:43:52 -0800 Subject: [PATCH 088/334] Restore GPUI test --- crates/gpui/src/text_system/line_wrapper.rs | 131 ++++++++++---------- 1 file changed, 63 insertions(+), 68 deletions(-) diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index 79013adbb2c83cd0e8e816ca5ef7bb88618b5ae8..f6963dbfd4ed6dec3c467741a45d6c5531aa0788 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -137,7 +137,7 @@ impl Boundary { #[cfg(test)] mod tests { use super::*; - use crate::{font, TestAppContext, TestDispatcher}; + use crate::{font, TestAppContext, TestDispatcher, TextRun, WrapBoundary}; use rand::prelude::*; #[test] @@ -206,75 +206,70 @@ mod tests { }); } - // todo!("move this to a test on TextSystem::layout_text") - // todo! repeat this test - // #[test] - // fn test_wrap_shaped_line() { - // App::test().run(|cx| { - // let text_system = cx.text_system().clone(); + // For compatibility with the test macro + use crate as gpui; - // let normal = TextRun { - // len: 0, - // font: font("Helvetica"), - // color: Default::default(), - // underline: Default::default(), - // }; - // let bold = TextRun { - // len: 0, - // font: font("Helvetica").bold(), - // color: Default::default(), - // underline: Default::default(), - // }; + #[crate::test] + fn test_wrap_shaped_line(cx: &mut TestAppContext) { + cx.update(|cx| { + let text_system = cx.text_system().clone(); + + let normal = TextRun { + len: 0, + font: font("Helvetica"), + color: Default::default(), + underline: Default::default(), + background_color: None, + }; + let bold = TextRun { + len: 0, + font: font("Helvetica").bold(), + color: Default::default(), + underline: Default::default(), + background_color: None, + }; - // impl TextRun { - // fn with_len(&self, len: usize) -> Self { - // let mut this = self.clone(); - // this.len = len; - // this - // } - // } + impl TextRun { + fn with_len(&self, len: usize) -> Self { + let mut this = self.clone(); + this.len = len; + this + } + } - // let text = "aa bbb cccc ddddd eeee".into(); - // let lines = text_system - // .layout_text( - // &text, - // px(16.), - // &[ - // normal.with_len(4), - // bold.with_len(5), - // normal.with_len(6), - // bold.with_len(1), - // normal.with_len(7), - // ], - // None, - // ) - // .unwrap(); - // let line = &lines[0]; + let text = "aa bbb cccc ddddd eeee".into(); + let lines = text_system + .shape_text( + text, + px(16.), + &[ + normal.with_len(4), + bold.with_len(5), + normal.with_len(6), + bold.with_len(1), + normal.with_len(7), + ], + Some(px(72.)), + ) + .unwrap(); - // let mut wrapper = LineWrapper::new( - // text_system.font_id(&normal.font).unwrap(), - // px(16.), - // text_system.platform_text_system.clone(), - // ); - // assert_eq!( - // wrapper - // .wrap_shaped_line(&text, &line, px(72.)) - // .collect::>(), - // &[ - // ShapedBoundary { - // run_ix: 1, - // glyph_ix: 3 - // }, - // ShapedBoundary { - // run_ix: 2, - // glyph_ix: 3 - // }, - // ShapedBoundary { - // run_ix: 4, - // glyph_ix: 2 - // } - // ], - // ); - // }); - // } + assert_eq!( + lines[0].layout.wrap_boundaries(), + &[ + WrapBoundary { + run_ix: 1, + glyph_ix: 3 + }, + WrapBoundary { + run_ix: 2, + glyph_ix: 3 + }, + WrapBoundary { + run_ix: 4, + glyph_ix: 2 + } + ], + ); + }); + } } From f71a0cddb87796bb1b45166322a8ed76195354e1 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 12:13:12 -0800 Subject: [PATCH 089/334] Remove last todos --- crates/gpui/src/action.rs | 1 - crates/theme/src/styles/players.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index b9cdd4a8bce81ebb4c26178b40a665720985343e..ef02316f83ea9dfa57c68106f6b5706b755e3cd6 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -128,7 +128,6 @@ impl ActionRegistry { } fn insert_action(&mut self, action: ActionData) { - //todo!(remove) let name: SharedString = action.name.into(); self.builders_by_name.insert(name.clone(), action.build); self.names_by_type_id.insert(action.type_id, name.clone()); diff --git a/crates/theme/src/styles/players.rs b/crates/theme/src/styles/players.rs index b2b797db08b270513b77809437184f9b782af77d..508b091b8b69c5e3ffb995e87862bffe0cbf5ae5 100644 --- a/crates/theme/src/styles/players.rs +++ b/crates/theme/src/styles/players.rs @@ -22,7 +22,7 @@ pub struct PlayerColors(pub Vec); impl Default for PlayerColors { /// Don't use this! /// We have to have a default to be `[refineable::Refinable]`. - /// todo!("Find a way to not need this for Refinable") + /// TODO "Find a way to not need this for Refinable" fn default() -> Self { Self::dark() } From 9df29fb3479eed3489077ac924b281186f7c53f1 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 10 Jan 2024 17:04:26 -0500 Subject: [PATCH 090/334] WIP --- crates/client/src/telemetry.rs | 7 +------ crates/terminal_view/src/terminal_element.rs | 17 +++++++++++++++-- crates/terminal_view/src/terminal_view.rs | 4 ++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 6db434cffb625dafd379d95dbdcd7b540889b38b..86cfdfcb4ddd50d8899345ae9014c52d44f56e52 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,7 +1,3 @@ -// TODO - Test if locking slows Zed typing down -// TODO - Make sure to send last event on flush -// TODO - Move code to be used as arcs in editor and terminal - mod event_coalescer; use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; @@ -408,8 +404,8 @@ impl Telemetry { pub fn log_edit_event(self: &Arc, environment: &'static str) { let mut state = self.state.lock(); - let coalesced_duration = state.edit_activity.log_event(environment); + drop(state); if let Some((start, end)) = coalesced_duration { let event = Event::Edit { @@ -418,7 +414,6 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - drop(state); self.report_event(event); } } diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index c52dbcb3d8e453729bd0d1ab57dc8da94e131f58..3e72acc51bd4b442514e61c18d4755fa868f8fe6 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -6,7 +6,7 @@ use gpui::{ InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, PlatformInputHandler, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, - WhiteSpace, WindowContext, + WeakView, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -24,6 +24,7 @@ use terminal::{ }; use theme::{ActiveTheme, Theme, ThemeSettings}; use ui::Tooltip; +use workspace::Workspace; use std::mem; use std::{fmt::Debug, ops::RangeInclusive}; @@ -142,6 +143,7 @@ impl LayoutRect { ///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection? pub struct TerminalElement { terminal: Model, + workspace: WeakView, focus: FocusHandle, focused: bool, cursor_visible: bool, @@ -160,6 +162,7 @@ impl StatefulInteractiveElement for TerminalElement {} impl TerminalElement { pub fn new( terminal: Model, + workspace: WeakView, focus: FocusHandle, focused: bool, cursor_visible: bool, @@ -167,6 +170,7 @@ impl TerminalElement { ) -> TerminalElement { TerminalElement { terminal, + workspace, focused, focus: focus.clone(), cursor_visible, @@ -762,6 +766,7 @@ impl Element for TerminalElement { .cursor .as_ref() .map(|cursor| cursor.bounding_rect(origin)), + workspace: self.workspace.clone(), }; self.register_mouse_listeners(origin, layout.mode, bounds, cx); @@ -831,6 +836,7 @@ impl IntoElement for TerminalElement { struct TerminalInputHandler { cx: AsyncWindowContext, terminal: Model, + workspace: WeakView, cursor_bounds: Option>, } @@ -871,7 +877,14 @@ impl PlatformInputHandler for TerminalInputHandler { .update(|_, cx| { self.terminal.update(cx, |terminal, _| { terminal.input(text.into()); - }) + }); + + self.workspace + .update(cx, |this, cx| { + let telemetry = this.project().read(cx).client().telemetry().clone(); + telemetry.log_edit_event("terminal"); + }) + .ok(); }) .ok(); } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index b4a273dd0bc9085c4a6b3b069589ef1d9d640c5d..ced122402f138e5f5b964792d7a1561260b063b6 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -73,6 +73,7 @@ pub fn init(cx: &mut AppContext) { ///A terminal view, maintains the PTY's file handles and communicates with the terminal pub struct TerminalView { terminal: Model, + workspace: WeakView, focus_handle: FocusHandle, has_new_content: bool, //Currently using iTerm bell, show bell emoji in tab until input is received @@ -135,6 +136,7 @@ impl TerminalView { workspace_id: WorkspaceId, cx: &mut ViewContext, ) -> Self { + let workspace_handle = workspace.clone(); cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); cx.subscribe(&terminal, move |this, _, event, cx| match event { Event::Wakeup => { @@ -279,6 +281,7 @@ impl TerminalView { Self { terminal, + workspace: workspace_handle, has_new_content: true, has_bell: false, focus_handle: cx.focus_handle(), @@ -661,6 +664,7 @@ impl Render for TerminalView { // TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu div().size_full().child(TerminalElement::new( terminal_handle, + self.workspace.clone(), self.focus_handle.clone(), focused, self.should_show_cursor(focused, cx), From a4ef1bc096792e94610ea2bdb38fff1d887db71b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 10 Jan 2024 23:06:10 +0100 Subject: [PATCH 091/334] Rename reuse_geometry to reuse_view --- crates/gpui/src/scene.rs | 2 +- crates/gpui/src/view.rs | 2 +- crates/gpui/src/window.rs | 82 ++++++++++++++++----------------------- 3 files changed, 35 insertions(+), 51 deletions(-) diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 9c63f803254b8f099be1cb7c369b41458a253df2..4b716413f50b8c0458023ce2cea926cb206376fa 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -125,7 +125,7 @@ impl Scene { } } - pub fn insert_views_from_scene(&mut self, views: &FxHashSet, prev_scene: &mut Self) { + pub fn reuse_views(&mut self, views: &FxHashSet, prev_scene: &mut Self) { for shadow in prev_scene.shadows.drain(..) { if views.contains(&EntityId::from(shadow.view_id as u64)) { let order = &prev_scene.orders_by_layer[&shadow.layer_id]; diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 22443a939546d9c86a534d62407ca18d090e3db4..961c6aef64cde75b6ea1554a0dd6c16f1c8326af 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -292,7 +292,7 @@ impl Element for AnyView { && !cx.window.dirty_views.contains(&self.entity_id()) && !cx.window.refreshing { - cx.reuse_geometry(); + cx.reuse_view(); return; } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index be7391495174252a190bc69dadb6c7d1cc820d2a..2e84260b9e45aa89593e568296c623750189a3a8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -347,6 +347,37 @@ impl Frame { .map(|focus_id| self.dispatch_tree.focus_path(focus_id)) .unwrap_or_default() } + + fn reuse_views(&mut self, prev_frame: &mut Self) { + // Reuse mouse listeners that didn't change since the last frame. + for (type_id, listeners) in &mut prev_frame.mouse_listeners { + let next_listeners = self.mouse_listeners.entry(*type_id).or_default(); + for (order, view_id, listener) in listeners.drain(..) { + if self.reused_views.contains(&view_id) { + next_listeners.push((order, view_id, listener)); + } + } + } + + // Reuse entries in the depth map that didn't change since the last frame. + for (order, view_id, bounds) in prev_frame.depth_map.drain(..) { + if self.reused_views.contains(&view_id) { + self.depth_map.push((order, view_id, bounds)); + } + } + self.depth_map.sort_by(|a, b| a.0.cmp(&b.0)); + + // Retain element states for views that didn't change since the last frame. + for (element_id, state) in prev_frame.element_states.drain() { + if self.reused_views.contains(&state.parent_view_id) { + self.element_states.entry(element_id).or_insert(state); + } + } + + // Reuse geometry that didn't change since the last frame. + self.scene + .reuse_views(&self.reused_views, &mut prev_frame.scene); + } } impl Window { @@ -1376,7 +1407,7 @@ impl<'a> WindowContext<'a> { ); } - pub(crate) fn reuse_geometry(&mut self) { + pub(crate) fn reuse_view(&mut self) { let view_id = self.parent_view_id().unwrap(); let window = &mut self.window; let grafted_view_ids = window @@ -1450,56 +1481,9 @@ impl<'a> WindowContext<'a> { self.window.next_frame.window_active = self.window.active; self.window.root_view = Some(root_view); - // Reuse mouse listeners that didn't change since the last frame. - for (type_id, listeners) in &mut self.window.rendered_frame.mouse_listeners { - let next_listeners = self - .window - .next_frame - .mouse_listeners - .entry(*type_id) - .or_default(); - for (order, view_id, listener) in listeners.drain(..) { - if self.window.next_frame.reused_views.contains(&view_id) { - next_listeners.push((order, view_id, listener)); - } - } - } - - // Reuse entries in the depth map that didn't change since the last frame. - for (order, view_id, bounds) in self.window.rendered_frame.depth_map.drain(..) { - if self.window.next_frame.reused_views.contains(&view_id) { - self.window - .next_frame - .depth_map - .push((order, view_id, bounds)); - } - } self.window .next_frame - .depth_map - .sort_by(|a, b| a.0.cmp(&b.0)); - - // Retain element states for views that didn't change since the last frame. - for (element_id, state) in self.window.rendered_frame.element_states.drain() { - if self - .window - .next_frame - .reused_views - .contains(&state.parent_view_id) - { - self.window - .next_frame - .element_states - .entry(element_id) - .or_insert(state); - } - } - - // Reuse geometry that didn't change since the last frame. - self.window.next_frame.scene.insert_views_from_scene( - &self.window.next_frame.reused_views, - &mut self.window.rendered_frame.scene, - ); + .reuse_views(&mut self.window.rendered_frame); self.window.next_frame.scene.finish(); let previous_focus_path = self.window.rendered_frame.focus_path(); From 7b3e7ee3ccf9ae662ba572ae11b4e825f321b897 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 10 Jan 2024 23:23:52 +0200 Subject: [PATCH 092/334] Enfoce no dbg! and todo! in Rust code via clippy lints in the CI job --- .github/actions/check_formatting/action.yml | 15 ------------ .github/actions/check_style/action.yml | 24 +++++++++++++++++++ .github/workflows/ci.yml | 11 +++++---- .github/workflows/release_nightly.yml | 11 +++++---- .../collab/src/tests/channel_guest_tests.rs | 10 ++++---- crates/vim/src/editor_events.rs | 1 - 6 files changed, 40 insertions(+), 32 deletions(-) delete mode 100644 .github/actions/check_formatting/action.yml create mode 100644 .github/actions/check_style/action.yml diff --git a/.github/actions/check_formatting/action.yml b/.github/actions/check_formatting/action.yml deleted file mode 100644 index 7fef26407bd866babfb10c1aac6222968f54c1fd..0000000000000000000000000000000000000000 --- a/.github/actions/check_formatting/action.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: 'Check formatting' -description: 'Checks code formatting use cargo fmt' - -runs: - using: "composite" - steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml new file mode 100644 index 0000000000000000000000000000000000000000..ff0481980816851ef6135c78f84b13b0cb21c09a --- /dev/null +++ b/.github/actions/check_style/action.yml @@ -0,0 +1,24 @@ +name: "Check formatting" +description: "Checks code formatting use cargo fmt" + +runs: + using: "composite" + steps: + - name: Install Rust + shell: bash -euxo pipefail {0} + run: | + rustup set profile minimal + rustup update stable + rustup component add clippy + + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check + + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + cargo clippy --workspace --all-targets --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ba25dbf947c14b485e7303cd12dd17f8a206927..3a92a744bb12d59fcf0539a4f8fb10f270899dd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,8 +22,8 @@ env: RUST_BACKTRACE: 1 jobs: - rustfmt: - name: Check formatting + style: + name: Check formatting and Clippy lints runs-on: - self-hosted - test @@ -33,19 +33,20 @@ jobs: with: clean: false submodules: "recursive" + fetch-depth: 0 - name: Set up default .cargo/config.toml run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml - - name: Run rustfmt - uses: ./.github/actions/check_formatting + - name: Run style checks + uses: ./.github/actions/check_style tests: name: Run tests runs-on: - self-hosted - test - needs: rustfmt + needs: style steps: - name: Checkout repo uses: actions/checkout@v3 diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index b7e6a0321e68b0ffe5c0521befcbfc13a76677a8..5d2dbe41f9aeceb18e0c35a064325dfd9bd4b31a 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -14,8 +14,8 @@ env: RUST_BACKTRACE: 1 jobs: - rustfmt: - name: Check formatting + style: + name: Check formatting and Clippy lints runs-on: - self-hosted - test @@ -25,16 +25,17 @@ jobs: with: clean: false submodules: "recursive" + fetch-depth: 0 - - name: Run rustfmt - uses: ./.github/actions/check_formatting + - name: Run style checks + uses: ./.github/actions/check_style tests: name: Run tests runs-on: - self-hosted - test - needs: rustfmt + needs: style steps: - name: Checkout repo uses: actions/checkout@v3 diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 9b68ce3922ab24726130c356636dae7d6899ef35..d5933235926fa1da8c1e8538dedd2a7506504cd8 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -104,12 +104,10 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); - assert!(dbg!( - room_b - .update(cx_b, |room, cx| room.share_microphone(cx)) - .await - ) - .is_err()); + assert!(room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .is_err()); // B is promoted active_call_a diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index e3ed076698d101a12f92c16048748532ea596e1c..e4057792796cad86d7fe31ff01b78ea9434ae102 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -111,7 +111,6 @@ mod test { let mut cx1 = VisualTestContext::from_window(cx.window, &cx); let editor1 = cx.editor.clone(); - dbg!(editor1.entity_id()); let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); From e0dd5a5820ff822357c35b2abfe2684c15ad97f7 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 00:33:53 +0200 Subject: [PATCH 093/334] Debugging --- .github/actions/check_style/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index ff0481980816851ef6135c78f84b13b0cb21c09a..cb567867e4cb53898e05c5d42dc0317cfa35e55c 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -21,4 +21,4 @@ runs: # so specify those here, and disable the rest until Zed's workspace # will have more fixes & suppression for the standard lint set run: | - cargo clippy --workspace --all-targets --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + CARGO_LOG=debug cargo -vvv clippy --workspace --all-targets --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo From 2e9c9adfbe35918846c10290eb4448021966ca2a Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 00:34:18 +0200 Subject: [PATCH 094/334] Remove active call data when it was accepted That hopefully helps with call notifications sometimes not being closed co-authored-by: Max --- crates/call/src/call.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 3561cc33852a84d78ed21371432743d9dc540862..3257906386eb23a9639536efc3367999b1932d58 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -282,7 +282,7 @@ impl ActiveCall { return Task::ready(Err(anyhow!("cannot join while on another call"))); } - let call = if let Some(call) = self.incoming_call.1.borrow().clone() { + let call = if let Some(call) = self.incoming_call.0.borrow_mut().take() { call } else { return Task::ready(Err(anyhow!("no incoming call"))); From b4444bdfc0e781d139ee279ee3a1bd9b7a8a2098 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 10 Jan 2024 17:41:02 -0500 Subject: [PATCH 095/334] Rename field in telemetry struct --- crates/client/src/telemetry.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 86cfdfcb4ddd50d8899345ae9014c52d44f56e52..d6be4fad2898b6bcdd725636038a3b88f00cb1ef 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -37,7 +37,7 @@ struct TelemetryState { log_file: Option, is_staff: Option, first_event_datetime: Option>, - edit_activity: EventCoalescer, + event_coalescer: EventCoalescer, } const EVENTS_URL_PATH: &'static str = "/api/events"; @@ -164,7 +164,7 @@ impl Telemetry { log_file: None, is_staff: None, first_event_datetime: None, - edit_activity: EventCoalescer::new(), + event_coalescer: EventCoalescer::new(), })); cx.observe_global::({ @@ -404,7 +404,7 @@ impl Telemetry { pub fn log_edit_event(self: &Arc, environment: &'static str) { let mut state = self.state.lock(); - let coalesced_duration = state.edit_activity.log_event(environment); + let coalesced_duration = state.event_coalescer.log_event(environment); drop(state); if let Some((start, end)) = coalesced_duration { From 766a869208cf9bf2abd6b721a66c9efc367096f8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 15:51:13 -0700 Subject: [PATCH 096/334] Fix fold-related panic --- crates/editor/src/element.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7efb43bd4852fc7ef1d8e5a5a43a79ad72a0303e..7b33a3239d05e7a6e73aa072e3b946d057253d0f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -878,16 +878,23 @@ impl EditorElement { let fold_corner_radius = 0.15 * layout.position_map.line_height; cx.with_element_id(Some("folds"), |cx| { let snapshot = &layout.position_map.snapshot; + for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) { let fold_range = fold.range.clone(); let display_range = fold.range.start.to_display_point(&snapshot) ..fold.range.end.to_display_point(&snapshot); debug_assert_eq!(display_range.start.row(), display_range.end.row()); let row = display_range.start.row(); + debug_assert!(row < layout.visible_display_row_range.end); + let Some(line_layout) = &layout + .position_map + .line_layouts + .get((row - layout.visible_display_row_range.start) as usize) + .map(|l| &l.line) + else { + continue; + }; - let line_layout = &layout.position_map.line_layouts - [(row - layout.visible_display_row_range.start) as usize] - .line; let start_x = content_origin.x + line_layout.x_for_index(display_range.start.column() as usize) - layout.position_map.scroll_position.x; From 0dca67fc33f32a0795aa468393226e68d5a904a9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 13:04:26 -0800 Subject: [PATCH 097/334] Add --top flag to zed-local script, for making windows take up half the screen --- script/zed-local | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/script/zed-local b/script/zed-local index 8ba1561bbda31482191945f018c6a29befb12925..4519ede38cf06bab355d0e65c530b76426e9108c 100755 --- a/script/zed-local +++ b/script/zed-local @@ -4,20 +4,28 @@ const { spawn, execFileSync } = require("child_process"); const RESOLUTION_REGEX = /(\d+) x (\d+)/; const DIGIT_FLAG_REGEX = /^--?(\d+)$/; -const RELEASE_MODE = "--release"; - -const args = process.argv.slice(2); // Parse the number of Zed instances to spawn. let instanceCount = 1; -const digitMatch = args[0]?.match(DIGIT_FLAG_REGEX); -if (digitMatch) { - instanceCount = parseInt(digitMatch[1]); - args.shift(); -} -const isReleaseMode = args.some((arg) => arg === RELEASE_MODE); -if (instanceCount > 4) { - throw new Error("Cannot spawn more than 4 instances"); +let isReleaseMode = false; +let isTop = false; + +const args = process.argv.slice(2); +for (const arg of args) { + const digitMatch = arg.match(DIGIT_FLAG_REGEX); + if (digitMatch) { + instanceCount = parseInt(digitMatch[1]); + continue; + } + + if (arg == "--release") { + isReleaseMode = true; + continue; + } + + if (arg == "--top") { + isTop = true; + } } // Parse the resolution of the main screen @@ -34,7 +42,11 @@ if (!mainDisplayResolution) { throw new Error("Could not parse screen resolution"); } const screenWidth = parseInt(mainDisplayResolution[1]); -const screenHeight = parseInt(mainDisplayResolution[2]); +let screenHeight = parseInt(mainDisplayResolution[2]); + +if (isTop) { + screenHeight = Math.floor(screenHeight / 2); +} // Determine the window size for each instance let instanceWidth = screenWidth; From 2d1eb0c56c3038fcfa7fd1117312b889c9090184 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 13:11:59 -0800 Subject: [PATCH 098/334] Expose a single `updates` stream from live_kit_client::Room Co-authored-by: Julia --- crates/call/src/room.rs | 80 ++++++------------- crates/live_kit_client/examples/test_app.rs | 35 ++++---- crates/live_kit_client/src/live_kit_client.rs | 20 +++++ crates/live_kit_client/src/prod.rs | 72 +++++------------ crates/live_kit_client/src/test.rs | 61 ++++---------- 5 files changed, 95 insertions(+), 173 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 3d1f1e70c74ab481864884e37a33e6db03ba5b05..877afceff3fa12bd3e0f7b6a42f78f141f7e89a5 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -15,10 +15,7 @@ use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use language::LanguageRegistry; -use live_kit_client::{ - LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, - RemoteVideoTrackUpdate, -}; +use live_kit_client::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate}; use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use settings::Settings as _; @@ -131,30 +128,11 @@ impl Room { } }); - let _maintain_video_tracks = cx.spawn({ + let _handle_updates = cx.spawn({ let room = room.clone(); move |this, mut cx| async move { - let mut track_video_changes = room.remote_video_track_updates(); - while let Some(track_change) = track_video_changes.next().await { - let this = if let Some(this) = this.upgrade() { - this - } else { - break; - }; - - this.update(&mut cx, |this, cx| { - this.remote_video_track_updated(track_change, cx).log_err() - }) - .ok(); - } - } - }); - - let _maintain_audio_tracks = cx.spawn({ - let room = room.clone(); - |this, mut cx| async move { - let mut track_audio_changes = room.remote_audio_track_updates(); - while let Some(track_change) = track_audio_changes.next().await { + let mut updates = room.updates(); + while let Some(update) = updates.next().await { let this = if let Some(this) = this.upgrade() { this } else { @@ -162,7 +140,7 @@ impl Room { }; this.update(&mut cx, |this, cx| { - this.remote_audio_track_updated(track_change, cx).log_err() + this.live_kit_room_updated(update, cx).log_err() }) .ok(); } @@ -195,7 +173,7 @@ impl Room { deafened: false, speaking: false, _maintain_room, - _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks], + _handle_updates, }) } else { None @@ -877,8 +855,8 @@ impl Room { .remote_audio_track_publications(&user.id.to_string()); for track in video_tracks { - this.remote_video_track_updated( - RemoteVideoTrackUpdate::Subscribed(track), + this.live_kit_room_updated( + RoomUpdate::SubscribedToRemoteVideoTrack(track), cx, ) .log_err(); @@ -887,8 +865,8 @@ impl Room { for (track, publication) in audio_tracks.iter().zip(publications.iter()) { - this.remote_audio_track_updated( - RemoteAudioTrackUpdate::Subscribed( + this.live_kit_room_updated( + RoomUpdate::SubscribedToRemoteAudioTrack( track.clone(), publication.clone(), ), @@ -979,13 +957,13 @@ impl Room { } } - fn remote_video_track_updated( + fn live_kit_room_updated( &mut self, - change: RemoteVideoTrackUpdate, + update: RoomUpdate, cx: &mut ModelContext, ) -> Result<()> { - match change { - RemoteVideoTrackUpdate::Subscribed(track) => { + match update { + RoomUpdate::SubscribedToRemoteVideoTrack(track) => { let user_id = track.publisher_id().parse()?; let track_id = track.sid().to_string(); let participant = self @@ -997,7 +975,8 @@ impl Room { participant_id: participant.peer_id, }); } - RemoteVideoTrackUpdate::Unsubscribed { + + RoomUpdate::UnsubscribedFromRemoteVideoTrack { publisher_id, track_id, } => { @@ -1011,19 +990,8 @@ impl Room { participant_id: participant.peer_id, }); } - } - - cx.notify(); - Ok(()) - } - fn remote_audio_track_updated( - &mut self, - change: RemoteAudioTrackUpdate, - cx: &mut ModelContext, - ) -> Result<()> { - match change { - RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } => { + RoomUpdate::ActiveSpeakersChanged { speakers } => { let mut speaker_ids = speakers .into_iter() .filter_map(|speaker_sid| speaker_sid.parse().ok()) @@ -1045,9 +1013,9 @@ impl Room { } } } - cx.notify(); } - RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => { + + RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } => { let mut found = false; for participant in &mut self.remote_participants.values_mut() { for track in participant.audio_tracks.values() { @@ -1061,10 +1029,9 @@ impl Room { break; } } - - cx.notify(); } - RemoteAudioTrackUpdate::Subscribed(track, publication) => { + + RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => { let user_id = track.publisher_id().parse()?; let track_id = track.sid().to_string(); let participant = self @@ -1078,7 +1045,8 @@ impl Room { participant_id: participant.peer_id, }); } - RemoteAudioTrackUpdate::Unsubscribed { + + RoomUpdate::UnsubscribedFromRemoteAudioTrack { publisher_id, track_id, } => { @@ -1597,7 +1565,7 @@ struct LiveKitRoom { speaking: bool, next_publish_id: usize, _maintain_room: Task<()>, - _maintain_tracks: [Task<()>; 2], + _handle_updates: Task<()>, } impl LiveKitRoom { diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index 68a8a84209b1df63f81f972918a7f1dc63e77a74..9fc8aafd30c283df748796790964dab11151d9af 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -2,9 +2,7 @@ use std::{sync::Arc, time::Duration}; use futures::StreamExt; use gpui::{actions, KeyBinding, Menu, MenuItem}; -use live_kit_client::{ - LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, -}; +use live_kit_client::{LocalAudioTrack, LocalVideoTrack, Room, RoomUpdate}; use live_kit_server::token::{self, VideoGrant}; use log::LevelFilter; use simplelog::SimpleLogger; @@ -60,12 +58,12 @@ fn main() { let room_b = Room::new(); room_b.connect(&live_kit_url, &user2_token).await.unwrap(); - let mut audio_track_updates = room_b.remote_audio_track_updates(); + let mut room_updates = room_b.updates(); let audio_track = LocalAudioTrack::create(); let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); - if let RemoteAudioTrackUpdate::Subscribed(track, _) = - audio_track_updates.next().await.unwrap() + if let RoomUpdate::SubscribedToRemoteAudioTrack(track, _) = + room_updates.next().await.unwrap() { let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); assert_eq!(remote_tracks.len(), 1); @@ -78,8 +76,8 @@ fn main() { audio_track_publication.set_mute(true).await.unwrap(); println!("waiting for mute changed!"); - if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = - audio_track_updates.next().await.unwrap() + if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = + room_updates.next().await.unwrap() { let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); assert_eq!(remote_tracks[0].sid(), track_id); @@ -90,8 +88,8 @@ fn main() { audio_track_publication.set_mute(false).await.unwrap(); - if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = - audio_track_updates.next().await.unwrap() + if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = + room_updates.next().await.unwrap() { let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); assert_eq!(remote_tracks[0].sid(), track_id); @@ -110,13 +108,13 @@ fn main() { room_a.unpublish_track(audio_track_publication); // Clear out any active speakers changed messages - let mut next = audio_track_updates.next().await.unwrap(); - while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { + let mut next = room_updates.next().await.unwrap(); + while let RoomUpdate::ActiveSpeakersChanged { speakers } = next { println!("Speakers changed: {:?}", speakers); - next = audio_track_updates.next().await.unwrap(); + next = room_updates.next().await.unwrap(); } - if let RemoteAudioTrackUpdate::Unsubscribed { + if let RoomUpdate::UnsubscribedFromRemoteAudioTrack { publisher_id, track_id, } = next @@ -128,7 +126,6 @@ fn main() { panic!("unexpected message"); } - let mut video_track_updates = room_b.remote_video_track_updates(); let displays = room_a.display_sources().await.unwrap(); let display = displays.into_iter().next().unwrap(); @@ -136,8 +133,8 @@ fn main() { let local_video_track_publication = room_a.publish_video_track(local_video_track).await.unwrap(); - if let RemoteVideoTrackUpdate::Subscribed(track) = - video_track_updates.next().await.unwrap() + if let RoomUpdate::SubscribedToRemoteVideoTrack(track) = + room_updates.next().await.unwrap() { let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); assert_eq!(remote_video_tracks.len(), 1); @@ -152,10 +149,10 @@ fn main() { .pop() .unwrap(); room_a.unpublish_track(local_video_track_publication); - if let RemoteVideoTrackUpdate::Unsubscribed { + if let RoomUpdate::UnsubscribedFromRemoteVideoTrack { publisher_id, track_id, - } = video_track_updates.next().await.unwrap() + } = room_updates.next().await.unwrap() { assert_eq!(publisher_id, "test-participant-1"); assert_eq!(remote_video_track.sid(), track_id); diff --git a/crates/live_kit_client/src/live_kit_client.rs b/crates/live_kit_client/src/live_kit_client.rs index 47cc3873ff0c1d4f7a3148878be32c12d8f98c5b..7052b107bc155044ed5c5a41dda7b4c7e5c8ba9e 100644 --- a/crates/live_kit_client/src/live_kit_client.rs +++ b/crates/live_kit_client/src/live_kit_client.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + #[cfg(not(any(test, feature = "test-support")))] pub mod prod; @@ -9,3 +11,21 @@ pub mod test; #[cfg(any(test, feature = "test-support"))] pub use test::*; + +pub type Sid = String; + +#[derive(Clone, Eq, PartialEq)] +pub enum ConnectionState { + Disconnected, + Connected { url: String, token: String }, +} + +#[derive(Clone)] +pub enum RoomUpdate { + ActiveSpeakersChanged { speakers: Vec }, + RemoteAudioTrackMuteChanged { track_id: Sid, muted: bool }, + SubscribedToRemoteVideoTrack(Arc), + SubscribedToRemoteAudioTrack(Arc, Arc), + UnsubscribedFromRemoteVideoTrack { publisher_id: Sid, track_id: Sid }, + UnsubscribedFromRemoteAudioTrack { publisher_id: Sid, track_id: Sid }, +} diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 5d8ef9bf134a61111247d3fac5b94fd87a50caf8..b9f5aa6aa8ac9a21e0d4554eabc634694152e12c 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -1,3 +1,4 @@ +use crate::{ConnectionState, RoomUpdate, Sid}; use anyhow::{anyhow, Context, Result}; use core_foundation::{ array::{CFArray, CFArrayRef}, @@ -155,22 +156,13 @@ extern "C" { fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; } -pub type Sid = String; - -#[derive(Clone, Eq, PartialEq)] -pub enum ConnectionState { - Disconnected, - Connected { url: String, token: String }, -} - pub struct Room { native_room: swift::Room, connection: Mutex<( watch::Sender, watch::Receiver, )>, - remote_audio_track_subscribers: Mutex>>, - remote_video_track_subscribers: Mutex>>, + update_subscribers: Mutex>>, _delegate: RoomDelegate, } @@ -181,8 +173,7 @@ impl Room { Self { native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), - remote_audio_track_subscribers: Default::default(), - remote_video_track_subscribers: Default::default(), + update_subscribers: Default::default(), _delegate: delegate, } }) @@ -397,15 +388,9 @@ impl Room { } } - pub fn remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver { - let (tx, rx) = mpsc::unbounded(); - self.remote_audio_track_subscribers.lock().push(tx); - rx - } - - pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver { + pub fn updates(&self) -> mpsc::UnboundedReceiver { let (tx, rx) = mpsc::unbounded(); - self.remote_video_track_subscribers.lock().push(tx); + self.update_subscribers.lock().push(tx); rx } @@ -416,8 +401,8 @@ impl Room { ) { let track = Arc::new(track); let publication = Arc::new(publication); - self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed( + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::SubscribedToRemoteAudioTrack( track.clone(), publication.clone(), )) @@ -426,8 +411,8 @@ impl Room { } fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) { - self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteAudioTrack { publisher_id: publisher_id.clone(), track_id: track_id.clone(), }) @@ -436,8 +421,8 @@ impl Room { } fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) { - self.remote_audio_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::RemoteAudioTrackMuteChanged { track_id: track_id.clone(), muted, }) @@ -445,29 +430,26 @@ impl Room { }); } - // A vec of publisher IDs fn active_speakers_changed(&self, speakers: Vec) { - self.remote_audio_track_subscribers - .lock() - .retain(move |tx| { - tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged { - speakers: speakers.clone(), - }) - .is_ok() - }); + self.update_subscribers.lock().retain(move |tx| { + tx.unbounded_send(RoomUpdate::ActiveSpeakersChanged { + speakers: speakers.clone(), + }) + .is_ok() + }); } fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) { let track = Arc::new(track); - self.remote_video_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone())) + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) .is_ok() }); } fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) { - self.remote_video_track_subscribers.lock().retain(|tx| { - tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteVideoTrack { publisher_id: publisher_id.clone(), track_id: track_id.clone(), }) @@ -889,18 +871,6 @@ impl Drop for RemoteVideoTrack { } } -pub enum RemoteVideoTrackUpdate { - Subscribed(Arc), - Unsubscribed { publisher_id: Sid, track_id: Sid }, -} - -pub enum RemoteAudioTrackUpdate { - ActiveSpeakersChanged { speakers: Vec }, - MuteChanged { track_id: Sid, muted: bool }, - Subscribed(Arc, Arc), - Unsubscribed { publisher_id: Sid, track_id: Sid }, -} - pub struct MacOSDisplay(swift::MacOSDisplay); impl MacOSDisplay { diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 4575fdd2c1845c53b29876d83f1fb6a8498717dc..9c1a5ec59a5dfb4a61aae84a3100e9d97fe374f3 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -1,3 +1,4 @@ +use crate::{ConnectionState, RoomUpdate, Sid}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{BTreeMap, HashMap}; @@ -104,9 +105,8 @@ impl TestServer { client_room .0 .lock() - .video_track_updates - .0 - .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .updates_tx + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) .unwrap(); } room.client_rooms.insert(identity, client_room); @@ -211,9 +211,8 @@ impl TestServer { let _ = client_room .0 .lock() - .video_track_updates - .0 - .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .updates_tx + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) .unwrap(); } } @@ -261,9 +260,8 @@ impl TestServer { let _ = client_room .0 .lock() - .audio_track_updates - .0 - .try_broadcast(RemoteAudioTrackUpdate::Subscribed( + .updates_tx + .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( track.clone(), publication.clone(), )) @@ -369,39 +367,26 @@ impl live_kit_server::api::Client for TestApiClient { } } -pub type Sid = String; - struct RoomState { connection: ( watch::Sender, watch::Receiver, ), display_sources: Vec, - audio_track_updates: ( - async_broadcast::Sender, - async_broadcast::Receiver, - ), - video_track_updates: ( - async_broadcast::Sender, - async_broadcast::Receiver, - ), -} - -#[derive(Clone, Eq, PartialEq)] -pub enum ConnectionState { - Disconnected, - Connected { url: String, token: String }, + updates_tx: async_broadcast::Sender, + updates_rx: async_broadcast::Receiver, } pub struct Room(Mutex); impl Room { pub fn new() -> Arc { + let (updates_tx, updates_rx) = async_broadcast::broadcast(128); Arc::new(Self(Mutex::new(RoomState { connection: watch::channel_with(ConnectionState::Disconnected), display_sources: Default::default(), - video_track_updates: async_broadcast::broadcast(128), - audio_track_updates: async_broadcast::broadcast(128), + updates_tx, + updates_rx, }))) } @@ -505,12 +490,8 @@ impl Room { .collect() } - pub fn remote_audio_track_updates(&self) -> impl Stream { - self.0.lock().audio_track_updates.1.clone() - } - - pub fn remote_video_track_updates(&self) -> impl Stream { - self.0.lock().video_track_updates.1.clone() + pub fn updates(&self) -> impl Stream { + self.0.lock().updates_rx.clone() } pub fn set_display_sources(&self, sources: Vec) { @@ -646,20 +627,6 @@ impl RemoteAudioTrack { } } -#[derive(Clone)] -pub enum RemoteVideoTrackUpdate { - Subscribed(Arc), - Unsubscribed { publisher_id: Sid, track_id: Sid }, -} - -#[derive(Clone)] -pub enum RemoteAudioTrackUpdate { - ActiveSpeakersChanged { speakers: Vec }, - MuteChanged { track_id: Sid, muted: bool }, - Subscribed(Arc, Arc), - Unsubscribed { publisher_id: Sid, track_id: Sid }, -} - #[derive(Clone)] pub struct MacOSDisplay { frames: ( From 75fdaeb56f7e71fffa79b8652a0f80cf0440ffe3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 10 Jan 2024 16:08:39 -0800 Subject: [PATCH 099/334] Detect when a track is unpublished due to reconnecting to livekit Co-authored-by: Julia --- crates/call/src/room.rs | 22 ++++++ .../Sources/LiveKitBridge/LiveKitBridge.swift | 50 ++++++++++++- crates/live_kit_client/src/live_kit_client.rs | 4 ++ crates/live_kit_client/src/prod.rs | 71 +++++++++++++++++++ crates/live_kit_client/src/test.rs | 65 +++++++++++++---- 5 files changed, 195 insertions(+), 17 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 877afceff3fa12bd3e0f7b6a42f78f141f7e89a5..04e883e68656a5c983af0f4f85f219b389c95567 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1060,6 +1060,28 @@ impl Room { participant_id: participant.peer_id, }); } + + RoomUpdate::LocalAudioTrackUnpublished { publication } => { + log::info!("unpublished audio track {}", publication.sid()); + if let Some(room) = &mut self.live_kit { + room.microphone_track = LocalTrack::None; + } + } + + RoomUpdate::LocalVideoTrackUnpublished { publication } => { + log::info!("unpublished video track {}", publication.sid()); + if let Some(room) = &mut self.live_kit { + room.screen_track = LocalTrack::None; + } + } + + RoomUpdate::LocalAudioTrackPublished { publication } => { + log::info!("published audio track {}", publication.sid()); + } + + RoomUpdate::LocalVideoTrackPublished { publication } => { + log::info!("published video track {}", publication.sid()); + } } cx.notify(); diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift index 5f22acf581686d0a3ffc3ae69962c00f271dccb1..db5da8e0e9ec5608e81fe34f3aa901d2e819f21d 100644 --- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -12,6 +12,8 @@ class LKRoomDelegate: RoomDelegate { var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + var onDidPublishOrUnpublishLocalAudioTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void + var onDidPublishOrUnpublishLocalVideoTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void init( data: UnsafeRawPointer, @@ -21,7 +23,10 @@ class LKRoomDelegate: RoomDelegate { onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, - onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void) + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, + onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void + ) { self.data = data self.onDidDisconnect = onDidDisconnect @@ -31,6 +36,8 @@ class LKRoomDelegate: RoomDelegate { self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack self.onActiveSpeakersChanged = onActiveSpeakersChanged + self.onDidPublishOrUnpublishLocalAudioTrack = onDidPublishOrUnpublishLocalAudioTrack + self.onDidPublishOrUnpublishLocalVideoTrack = onDidPublishOrUnpublishLocalVideoTrack } func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) { @@ -65,6 +72,22 @@ class LKRoomDelegate: RoomDelegate { self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString) } } + + func room(_ room: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) { + if publication.kind == .video { + self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) + } else if publication.kind == .audio { + self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) + } + } + + func room(_ room: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) { + if publication.kind == .video { + self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) + } else if publication.kind == .audio { + self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) + } + } } class LKVideoRenderer: NSObject, VideoRenderer { @@ -109,7 +132,9 @@ public func LKRoomDelegateCreate( onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, - onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, + onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void ) -> UnsafeMutableRawPointer { let delegate = LKRoomDelegate( data: data, @@ -119,7 +144,9 @@ public func LKRoomDelegateCreate( onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack, onActiveSpeakersChanged: onActiveSpeakerChanged, onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, - onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack + onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack, + onDidPublishOrUnpublishLocalAudioTrack: onDidPublishOrUnpublishLocalAudioTrack, + onDidPublishOrUnpublishLocalVideoTrack: onDidPublishOrUnpublishLocalVideoTrack ) return Unmanaged.passRetained(delegate).toOpaque() } @@ -292,6 +319,14 @@ public func LKLocalTrackPublicationSetMute( } } +@_cdecl("LKLocalTrackPublicationIsMuted") +public func LKLocalTrackPublicationIsMuted( + publication: UnsafeRawPointer +) -> Bool { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + return publication.muted +} + @_cdecl("LKRemoteTrackPublicationSetEnabled") public func LKRemoteTrackPublicationSetEnabled( publication: UnsafeRawPointer, @@ -325,3 +360,12 @@ public func LKRemoteTrackPublicationGetSid( return publication.sid as CFString } + +@_cdecl("LKLocalTrackPublicationGetSid") +public func LKLocalTrackPublicationGetSid( + publication: UnsafeRawPointer +) -> CFString { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.sid as CFString +} diff --git a/crates/live_kit_client/src/live_kit_client.rs b/crates/live_kit_client/src/live_kit_client.rs index 7052b107bc155044ed5c5a41dda7b4c7e5c8ba9e..abec27462e39639c9f46bd5bedfa7c2433cf10a5 100644 --- a/crates/live_kit_client/src/live_kit_client.rs +++ b/crates/live_kit_client/src/live_kit_client.rs @@ -28,4 +28,8 @@ pub enum RoomUpdate { SubscribedToRemoteAudioTrack(Arc, Arc), UnsubscribedFromRemoteVideoTrack { publisher_id: Sid, track_id: Sid }, UnsubscribedFromRemoteAudioTrack { publisher_id: Sid, track_id: Sid }, + LocalAudioTrackPublished { publication: LocalTrackPublication }, + LocalAudioTrackUnpublished { publication: LocalTrackPublication }, + LocalVideoTrackPublished { publication: LocalTrackPublication }, + LocalVideoTrackUnpublished { publication: LocalTrackPublication }, } diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index b9f5aa6aa8ac9a21e0d4554eabc634694152e12c..0827c0cbb41092f7643891c71c8b8c1f279a0860 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -77,6 +77,16 @@ extern "C" { publisher_id: CFStringRef, track_id: CFStringRef, ), + on_did_publish_or_unpublish_local_audio_track: extern "C" fn( + callback_data: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ), + on_did_publish_or_unpublish_local_video_track: extern "C" fn( + callback_data: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ), ) -> swift::RoomDelegate; fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room; @@ -152,7 +162,9 @@ extern "C" { callback_data: *mut c_void, ); + fn LKLocalTrackPublicationIsMuted(publication: swift::LocalTrackPublication) -> bool; fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool; + fn LKLocalTrackPublicationGetSid(publication: swift::LocalTrackPublication) -> CFStringRef; fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; } @@ -511,6 +523,8 @@ impl RoomDelegate { Self::on_active_speakers_changed, Self::on_did_subscribe_to_remote_video_track, Self::on_did_unsubscribe_from_remote_video_track, + Self::on_did_publish_or_unpublish_local_audio_track, + Self::on_did_publish_or_unpublish_local_video_track, ) }; Self { @@ -624,6 +638,46 @@ impl RoomDelegate { } let _ = Weak::into_raw(room); } + + extern "C" fn on_did_publish_or_unpublish_local_audio_track( + room: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + let publication = LocalTrackPublication::new(publication); + let update = if is_published { + RoomUpdate::LocalAudioTrackPublished { publication } + } else { + RoomUpdate::LocalAudioTrackUnpublished { publication } + }; + room.update_subscribers + .lock() + .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_publish_or_unpublish_local_video_track( + room: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + let publication = LocalTrackPublication::new(publication); + let update = if is_published { + RoomUpdate::LocalVideoTrackPublished { publication } + } else { + RoomUpdate::LocalVideoTrackUnpublished { publication } + }; + room.update_subscribers + .lock() + .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); + } + let _ = Weak::into_raw(room); + } } impl Drop for RoomDelegate { @@ -673,6 +727,10 @@ impl LocalTrackPublication { Self(native_track_publication) } + pub fn sid(&self) -> String { + unsafe { CFString::wrap_under_get_rule(LKLocalTrackPublicationGetSid(self.0)).to_string() } + } + pub fn set_mute(&self, muted: bool) -> impl Future> { let (tx, rx) = futures::channel::oneshot::channel(); @@ -697,6 +755,19 @@ impl LocalTrackPublication { async move { rx.await.unwrap() } } + + pub fn is_muted(&self) -> bool { + unsafe { LKLocalTrackPublicationIsMuted(self.0) } + } +} + +impl Clone for LocalTrackPublication { + fn clone(&self) -> Self { + unsafe { + CFRetain(self.0 .0); + } + Self(self.0) + } } impl Drop for LocalTrackPublication { diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 9c1a5ec59a5dfb4a61aae84a3100e9d97fe374f3..0716042ff196e1e0ea8f3543f03b645648ed473c 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -8,7 +8,14 @@ use live_kit_server::{proto, token}; use media::core_video::CVImageBuffer; use parking_lot::Mutex; use postage::watch; -use std::{future::Future, mem, sync::Arc}; +use std::{ + future::Future, + mem, + sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, + }, +}; static SERVERS: Mutex>> = Mutex::new(BTreeMap::new()); @@ -176,7 +183,11 @@ impl TestServer { } } - async fn publish_video_track(&self, token: String, local_track: LocalVideoTrack) -> Result<()> { + async fn publish_video_track( + &self, + token: String, + local_track: LocalVideoTrack, + ) -> Result { self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let identity = claims.sub.unwrap().to_string(); @@ -198,8 +209,9 @@ impl TestServer { return Err(anyhow!("user is not allowed to publish")); } + let sid = nanoid::nanoid!(17); let track = Arc::new(RemoteVideoTrack { - sid: nanoid::nanoid!(17), + sid: sid.clone(), publisher_id: identity.clone(), frames_rx: local_track.frames_rx.clone(), }); @@ -217,14 +229,14 @@ impl TestServer { } } - Ok(()) + Ok(sid) } async fn publish_audio_track( &self, token: String, _local_track: &LocalAudioTrack, - ) -> Result<()> { + ) -> Result { self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let identity = claims.sub.unwrap().to_string(); @@ -246,8 +258,9 @@ impl TestServer { return Err(anyhow!("user is not allowed to publish")); } + let sid = nanoid::nanoid!(17); let track = Arc::new(RemoteAudioTrack { - sid: nanoid::nanoid!(17), + sid: sid.clone(), publisher_id: identity.clone(), }); @@ -269,7 +282,7 @@ impl TestServer { } } - Ok(()) + Ok(sid) } fn video_tracks(&self, token: String) -> Result>> { @@ -425,10 +438,14 @@ impl Room { let this = self.clone(); let track = track.clone(); async move { - this.test_server() + let sid = this + .test_server() .publish_video_track(this.token(), track) .await?; - Ok(LocalTrackPublication) + Ok(LocalTrackPublication { + muted: Default::default(), + sid, + }) } } pub fn publish_audio_track( @@ -438,10 +455,14 @@ impl Room { let this = self.clone(); let track = track.clone(); async move { - this.test_server() + let sid = this + .test_server() .publish_audio_track(this.token(), &track) .await?; - Ok(LocalTrackPublication) + Ok(LocalTrackPublication { + muted: Default::default(), + sid, + }) } } @@ -536,11 +557,27 @@ impl Drop for Room { } } -pub struct LocalTrackPublication; +#[derive(Clone)] +pub struct LocalTrackPublication { + sid: String, + muted: Arc, +} impl LocalTrackPublication { - pub fn set_mute(&self, _mute: bool) -> impl Future> { - async { Ok(()) } + pub fn set_mute(&self, mute: bool) -> impl Future> { + let muted = self.muted.clone(); + async move { + muted.store(mute, SeqCst); + Ok(()) + } + } + + pub fn is_muted(&self) -> bool { + self.muted.load(SeqCst) + } + + pub fn sid(&self) -> String { + self.sid.clone() } } From 1932a298cb3758b0c909d98a676a78affac1d225 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 16:29:00 -0800 Subject: [PATCH 100/334] Add back ime_key --- crates/gpui/src/platform/mac/window.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index d2ce87f5fa43b8d5ea7011abd0ee7e42bd8dfa77..479e6acab284e8f3fdda45695bd1cc6125ffa031 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1114,7 +1114,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: // we don't match cmd/fn because they don't seem to use IME modifiers: Default::default(), key: ime_text.clone().unwrap(), - ime_key: None, // todo!("handle IME key") + ime_key: None, }, }; handled = callback(InputEvent::KeyDown(event_with_ime_text)); @@ -1568,6 +1568,9 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS replacement_range, text: text.to_string(), }); + if text.to_string().to_ascii_lowercase() != pending_key_down.0.keystroke.key { + pending_key_down.0.keystroke.ime_key = Some(text.to_string()); + } window_state.lock().pending_key_down = Some(pending_key_down); } } From bddf827bc1bd004aea1a655f56fb175d42b9b708 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 10 Jan 2024 21:02:34 -0500 Subject: [PATCH 101/334] Add reminder for the future --- crates/client/src/telemetry.rs | 2 +- crates/client/src/telemetry/event_coalescer.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index d6be4fad2898b6bcdd725636038a3b88f00cb1ef..628b5292c63c7f895e771c70a425ef7a44151f16 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -204,7 +204,7 @@ impl Telemetry { #[cfg(not(any(test, feature = "test-support")))] fn shutdown_telemetry(self: &Arc) -> impl Future { self.report_app_event("close"); - self.flush_events(); + // TODO: close final edit period and make sure it's sent Task::ready(()) } diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 6369ebccbc8b8530d1d4c8aa0416545cb6fff02c..96c61486b866c4521fe27eb8d9f3531cd5117661 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -26,6 +26,10 @@ impl EventCoalescer { self.log_event_with_time(Utc::now(), environment) } + // pub fn close_current_period(&mut self) -> Option<(DateTime, DateTime)> { + // self.environment.map(|env| self.log_event(env)).flatten() + // } + fn log_event_with_time( &mut self, log_time: DateTime, From a5ca58354d181dc17f86b5fce34a34aefe9fdbe2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 17:44:01 -0800 Subject: [PATCH 102/334] Fix first few asserts --- crates/gpui/src/window.rs | 3 +- crates/workspace/src/pane.rs | 4 +- crates/workspace/src/workspace.rs | 766 ++++++++++++++++-------------- 3 files changed, 410 insertions(+), 363 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 10c8651924118fe7f5050e3f50e21bf4ef54f274..25bfa799d2d2cdd6b3ae72d2607dbf53fe0e03dd 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1906,7 +1906,8 @@ impl<'a> WindowContext<'a> { .platform_window .on_should_close(Box::new(move || { this.update(|_, cx| { - // Ensure that the window is removed from the app if it's been closed. + // Ensure that the window is removed from the app if it's been closed + // by always pre-empting the system close event. if f(cx) { cx.remove_window(); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index dad7b50ca6c307aef780910ee03249633ccfbfda..c4602bb1adf91d89d70272c8d89082ffdda93b14 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -255,8 +255,8 @@ impl Pane { let focus_handle = cx.focus_handle(); let subscriptions = vec![ - cx.on_focus_in(&focus_handle, move |this, cx| this.focus_in(cx)), - cx.on_focus_out(&focus_handle, move |this, cx| this.focus_out(cx)), + cx.on_focus_in(&focus_handle, Pane::focus_in), + cx.on_focus_out(&focus_handle, Pane::focus_out), ]; let handle = cx.view().downgrade(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 839edf1009ab4c9397b16c9f9959bab89f8c169c..21a26d8c9981e92522d7b1b9386670e7f5a3c6f6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -672,7 +672,7 @@ impl Workspace { // ); // this.show_notification(1, cx, |cx| { - // cx.build_view(|_cx| { + // cx.new_view(|_cx| { // simple_message_notification::MessageNotification::new(format!("Error:")) // .with_click_message("click here because!") // }) @@ -4363,12 +4363,15 @@ mod tests { use std::{cell::RefCell, rc::Rc}; use super::*; - use crate::item::{ - test::{TestItem, TestProjectItem}, - ItemEvent, + use crate::{ + dock::{test::TestPanel, PanelEvent}, + item::{ + test::{TestItem, TestProjectItem}, + ItemEvent, + }, }; use fs::FakeFs; - use gpui::TestAppContext; + use gpui::{px, DismissEvent, TestAppContext, VisualTestContext}; use project::{Project, ProjectEntryId}; use serde_json::json; use settings::SettingsStore; @@ -4935,362 +4938,405 @@ mod tests { }); } - // #[gpui::test] - // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { - // init_test(cx); - // let fs = FakeFs::new(cx.executor()); - - // let project = Project::test(fs, [], cx).await; - // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); - - // let panel = workspace.update(cx, |workspace, cx| { - // let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); - // workspace.add_panel(panel.clone(), cx); - - // workspace - // .right_dock() - // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - - // panel - // }); - - // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); - // pane.update(cx, |pane, cx| { - // let item = cx.build_view(|cx| TestItem::new(cx)); - // pane.add_item(Box::new(item), true, true, None, cx); - // }); - - // // Transfer focus from center to panel - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Transfer focus from panel to center - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Close the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Open the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Focus and zoom panel - // panel.update(cx, |panel, cx| { - // cx.focus_self(); - // panel.set_zoomed(true, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Transfer focus to the center closes the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Transferring focus back to the panel keeps it zoomed - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Close the dock while it is zoomed - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(workspace.zoomed.is_none()); - // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Opening the dock, when it's zoomed, retains focus - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(workspace.zoomed.is_some()); - // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); - // }); - - // // Unzoom and close the panel, zoom the active pane. - // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); - - // // Opening a dock unzooms the pane. - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - // workspace.update(cx, |workspace, cx| { - // let pane = pane.read(cx); - // assert!(!pane.is_zoomed()); - // assert!(!pane.focus_handle(cx).is_focused(cx)); - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(workspace.zoomed.is_none()); - // }); - // } + #[gpui::test] + async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); - // #[gpui::test] - // async fn test_panels(cx: &mut gpui::TestAppContext) { - // init_test(cx); - // let fs = FakeFs::new(cx.executor()); - - // let project = Project::test(fs, [], cx).await; - // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); - - // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { - // // Add panel_1 on the left, panel_2 on the right. - // let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx)); - // workspace.add_panel(panel_1.clone(), cx); - // workspace - // .left_dock() - // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); - // let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); - // workspace.add_panel(panel_2.clone(), cx); - // workspace - // .right_dock() - // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - - // let left_dock = workspace.left_dock(); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id() - // ); - // assert_eq!( - // left_dock.read(cx).active_panel_size(cx).unwrap(), - // panel_1.size(cx) - // ); - - // left_dock.update(cx, |left_dock, cx| { - // left_dock.resize_active_panel(Some(1337.), cx) - // }); - // assert_eq!( - // workspace - // .right_dock() - // .read(cx) - // .visible_panel() - // .unwrap() - // .panel_id(), - // panel_2.panel_id(), - // ); - - // (panel_1, panel_2) - // }); - - // // Move panel_1 to the right - // panel_1.update(cx, |panel_1, cx| { - // panel_1.set_position(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. - // // Since it was the only panel on the left, the left dock should now be closed. - // assert!(!workspace.left_dock().read(cx).is_open()); - // assert!(workspace.left_dock().read(cx).visible_panel().is_none()); - // let right_dock = workspace.right_dock(); - // assert_eq!( - // right_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id() - // ); - // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); - - // // Now we move panel_2 to the left - // panel_2.set_position(DockPosition::Left, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_2 was not visible on the right, we don't open the left dock. - // assert!(!workspace.left_dock().read(cx).is_open()); - // // And the right dock is unaffected in it's displaying of panel_1 - // assert!(workspace.right_dock().read(cx).is_open()); - // assert_eq!( - // workspace - // .right_dock() - // .read(cx) - // .visible_panel() - // .unwrap() - // .panel_id(), - // panel_1.panel_id(), - // ); - // }); - - // // Move panel_1 back to the left - // panel_1.update(cx, |panel_1, cx| { - // panel_1.set_position(DockPosition::Left, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id() - // ); - // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); - // // And right the dock should be closed as it no longer has any panels. - // assert!(!workspace.right_dock().read(cx).is_open()); - - // // Now we move panel_1 to the bottom - // panel_1.set_position(DockPosition::Bottom, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the left, we close the left dock. - // assert!(!workspace.left_dock().read(cx).is_open()); - // // The bottom dock is sized based on the panel's default size, - // // since the panel orientation changed from vertical to horizontal. - // let bottom_dock = workspace.bottom_dock(); - // assert_eq!( - // bottom_dock.read(cx).active_panel_size(cx).unwrap(), - // panel_1.size(cx), - // ); - // // Close bottom dock and move panel_1 back to the left. - // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); - // panel_1.set_position(DockPosition::Left, cx); - // }); - - // // Emit activated event on panel 1 - // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate)); - - // // Now the left dock is open and panel_1 is active and focused. - // workspace.update(cx, |workspace, cx| { - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id(), - // ); - // assert!(panel_1.focus_handle(cx).is_focused(cx)); - // }); - - // // Emit closed event on panel 2, which is not active - // panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close)); - - // // Wo don't close the left dock, because panel_2 wasn't the active panel - // workspace.update(cx, |workspace, cx| { - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().panel_id(), - // panel_1.panel_id(), - // ); - // }); - - // // Emitting a ZoomIn event shows the panel as zoomed. - // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); - // }); - - // // Move panel to another dock while it is zoomed - // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); - - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // If focus is transferred to another view that's not a panel or another pane, we still show - // // the panel as zoomed. - // let other_focus_handle = cx.update(|cx| cx.focus_handle()); - // cx.update(|cx| cx.focus(&other_focus_handle)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. - // workspace.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // If focus is transferred again to another view that's not a panel or a pane, we won't - // // show the panel as zoomed because it wasn't zoomed before. - // cx.update(|cx| cx.focus(&other_focus_handle)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // When focus is transferred back to the panel, it is zoomed again. - // panel_1.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // Emitting a ZoomOut event unzooms the panel. - // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // Emit closed event on panel 1, which is active - // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close)); - - // // Now the left dock is closed, because panel_1 was the active panel - // workspace.update(cx, |workspace, cx| { - // let right_dock = workspace.right_dock(); - // assert!(!right_dock.read(cx).is_open()); - // }); - // } + let project = Project::test(fs, [], cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + + let panel = workspace.update(cx, |workspace, cx| { + let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx)); + workspace.add_panel(panel.clone(), cx); + + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + panel + }); + + let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + pane.update(cx, |pane, cx| { + let item = cx.new_view(|cx| TestItem::new(cx)); + pane.add_item(Box::new(item), true, true, None, cx); + }); + + // Transfer focus from center to panel + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Transfer focus from panel to center + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Close the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx); + }); + + workspace.update(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Open the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx); + }); + + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Focus and zoom panel + panel.update(cx, |panel, cx| { + cx.focus_self(); + panel.set_zoomed(true, cx) + }); + + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Transfer focus to the center closes the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + workspace.update(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Transferring focus back to the panel keeps it zoomed + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Close the dock while it is zoomed + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + + workspace.update(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(workspace.zoomed.is_none()); + assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Opening the dock, when it's zoomed, retains focus + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + + workspace.update(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(workspace.zoomed.is_some()); + assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + }); + + // Unzoom and close the panel, zoom the active pane. + panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); + + // Opening a dock unzooms the pane. + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + workspace.update(cx, |workspace, cx| { + let pane = pane.read(cx); + assert!(!pane.is_zoomed()); + assert!(!pane.focus_handle(cx).is_focused(cx)); + assert!(workspace.right_dock().read(cx).is_open()); + assert!(workspace.zoomed.is_none()); + }); + } + + struct TestModal(FocusHandle); + + impl TestModal { + fn new(cx: &mut ViewContext) -> Self { + Self(cx.focus_handle()) + } + } + + impl EventEmitter for TestModal {} + + impl FocusableView for TestModal { + fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { + self.0.clone() + } + } + + impl ModalView for TestModal {} + + impl Render for TestModal { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + div().track_focus(&self.0) + } + } + + #[gpui::test] + async fn test_panels(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + + let project = Project::test(fs, [], cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + + let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { + let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx)); + workspace.add_panel(panel_1.clone(), cx); + workspace + .left_dock() + .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); + let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx)); + workspace.add_panel(panel_2.clone(), cx); + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + let left_dock = workspace.left_dock(); + assert_eq!( + left_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id() + ); + assert_eq!( + left_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx) + ); + + left_dock.update(cx, |left_dock, cx| { + left_dock.resize_active_panel(Some(px(1337.)), cx) + }); + assert_eq!( + workspace + .right_dock() + .read(cx) + .visible_panel() + .unwrap() + .panel_id(), + panel_2.panel_id(), + ); + + (panel_1, panel_2) + }); + + // Move panel_1 to the right + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Right, cx) + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. + // Since it was the only panel on the left, the left dock should now be closed. + assert!(!workspace.left_dock().read(cx).is_open()); + assert!(workspace.left_dock().read(cx).visible_panel().is_none()); + let right_dock = workspace.right_dock(); + assert_eq!( + right_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id() + ); + assert_eq!( + right_dock.read(cx).active_panel_size(cx).unwrap(), + px(1337.) + ); + + // Now we move panel_2 to the left + panel_2.set_position(DockPosition::Left, cx); + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_2 was not visible on the right, we don't open the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // And the right dock is unaffected in it's displaying of panel_1 + assert!(workspace.right_dock().read(cx).is_open()); + assert_eq!( + workspace + .right_dock() + .read(cx) + .visible_panel() + .unwrap() + .panel_id(), + panel_1.panel_id(), + ); + }); + + // Move panel_1 back to the left + panel_1.update(cx, |panel_1, cx| { + panel_1.set_position(DockPosition::Left, cx) + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id() + ); + assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.)); + // And the right dock should be closed as it no longer has any panels. + assert!(!workspace.right_dock().read(cx).is_open()); + + // Now we move panel_1 to the bottom + panel_1.set_position(DockPosition::Bottom, cx); + }); + + workspace.update(cx, |workspace, cx| { + // Since panel_1 was visible on the left, we close the left dock. + assert!(!workspace.left_dock().read(cx).is_open()); + // The bottom dock is sized based on the panel's default size, + // since the panel orientation changed from vertical to horizontal. + let bottom_dock = workspace.bottom_dock(); + assert_eq!( + bottom_dock.read(cx).active_panel_size(cx).unwrap(), + panel_1.size(cx), + ); + // Close bottom dock and move panel_1 back to the left. + bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); + panel_1.set_position(DockPosition::Left, cx); + }); + + // Emit activated event on panel 1 + panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate)); + + // Now the left dock is open and panel_1 is active and focused. + workspace.update(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id(), + ); + assert!(panel_1.focus_handle(cx).is_focused(cx)); + }); + + // Emit closed event on panel 2, which is not active + panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + + // Wo don't close the left dock, because panel_2 wasn't the active panel + workspace.update(cx, |workspace, cx| { + let left_dock = workspace.left_dock(); + assert!(left_dock.read(cx).is_open()); + assert_eq!( + left_dock.read(cx).visible_panel().unwrap().panel_id(), + panel_1.panel_id(), + ); + }); + + // Emitting a ZoomIn event shows the panel as zoomed. + panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn)); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); + }); + + // Move panel to another dock while it is zoomed + panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + }); + + // This is a helper for getting a: + // - valid focus on an element, + // - that isn't a part of the panes and panels system of the Workspace, + // - and doesn't trigger the 'on_focus_lost' API. + let focus_other_view = { + let workspace = workspace.clone(); + move |cx: &mut VisualTestContext| { + workspace.update(cx, |workspace, cx| { + if let Some(_) = workspace.active_modal::(cx) { + workspace.toggle_modal(cx, TestModal::new); + workspace.toggle_modal(cx, TestModal::new); + } else { + workspace.toggle_modal(cx, TestModal::new); + } + }) + } + }; + + // If focus is transferred to another view that's not a panel or another pane, we still show + // the panel as zoomed. + focus_other_view(cx); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + }); + + // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. + workspace.update(cx, |_, cx| cx.focus_self()); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); + }); + + // If focus is transferred again to another view that's not a panel or a pane, we won't + // show the panel as zoomed because it wasn't zoomed before. + focus_other_view(cx); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); + }); + + // When the panel is activated, it is zoomed again. + cx.dispatch_action(ToggleRightDock); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + }); + + // Emitting a ZoomOut event unzooms the panel. + panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut)); + workspace.update(cx, |workspace, _| { + assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); + }); + + // Emit closed event on panel 1, which is active + panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + + // Now the left dock is closed, because panel_1 was the active panel + workspace.update(cx, |workspace, cx| { + let right_dock = workspace.right_dock(); + assert!(!right_dock.read(cx).is_open()); + }); + } pub fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { From 38396d4281ce8ced885a3be5b68ba26f9ebcbef0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 19:37:53 -0800 Subject: [PATCH 103/334] Add remaining tests co-authored-by: Conrad --- crates/feedback/src/feedback_modal.rs | 41 +- crates/gpui/src/action.rs | 2 +- crates/gpui/src/keymap/matcher.rs | 745 ++++++++++---------- crates/gpui/src/platform/mac/text_system.rs | 212 ++---- 4 files changed, 423 insertions(+), 577 deletions(-) diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index bf7a0715604f20ec6eae1d28ea4988016a8cd2cf..2444a8e94850f8128aafb0948cb356eee81f1086 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -525,43 +525,4 @@ impl Render for FeedbackModal { } } -// TODO: Testing of various button states, dismissal prompts, etc. - -// #[cfg(test)] -// mod test { -// use super::*; - -// #[test] -// fn test_invalid_email_addresses() { -// let markdown = markdown.await.log_err(); -// let buffer = project.update(&mut cx, |project, cx| { -// project.create_buffer("", markdown, cx) -// })??; - -// workspace.update(&mut cx, |workspace, cx| { -// let system_specs = SystemSpecs::new(cx); - -// workspace.toggle_modal(cx, move |cx| { -// let feedback_modal = FeedbackModal::new(system_specs, project, buffer, cx); - -// assert!(!feedback_modal.can_submit()); -// assert!(!feedback_modal.valid_email_address(cx)); -// assert!(!feedback_modal.valid_character_count()); - -// feedback_modal -// .email_address_editor -// .update(cx, |this, cx| this.set_text("a", cx)); -// feedback_modal.set_submission_state(cx); - -// assert!(!feedback_modal.valid_email_address(cx)); - -// feedback_modal -// .email_address_editor -// .update(cx, |this, cx| this.set_text("a&b.com", cx)); -// feedback_modal.set_submission_state(cx); - -// assert!(feedback_modal.valid_email_address(cx)); -// }); -// })?; -// } -// } +// TODO: Testing of various button states, dismissal prompts, etc. :) diff --git a/crates/gpui/src/action.rs b/crates/gpui/src/action.rs index ef02316f83ea9dfa57c68106f6b5706b755e3cd6..9caa0da4823f64f5c30c32619a7b6950b305e676 100644 --- a/crates/gpui/src/action.rs +++ b/crates/gpui/src/action.rs @@ -170,7 +170,7 @@ impl ActionRegistry { macro_rules! actions { ($namespace:path, [ $($name:ident),* $(,)? ]) => { $( - #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::private::serde_derive::Deserialize)] + #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)] #[serde(crate = "gpui::private::serde")] pub struct $name; diff --git a/crates/gpui/src/keymap/matcher.rs b/crates/gpui/src/keymap/matcher.rs index ab42f1278c3a837c2895d77fdf388a842ed2433b..fb508766a1f4386d3b4064d373b40f3ed42ce48d 100644 --- a/crates/gpui/src/keymap/matcher.rs +++ b/crates/gpui/src/keymap/matcher.rs @@ -28,11 +28,11 @@ impl KeystrokeMatcher { /// Pushes a keystroke onto the matcher. /// The result of the new keystroke is returned: - /// KeyMatch::None => + /// - KeyMatch::None => /// No match is valid for this key given any pending keystrokes. - /// KeyMatch::Pending => + /// - KeyMatch::Pending => /// There exist bindings which are still waiting for more keys. - /// KeyMatch::Complete(matches) => + /// - KeyMatch::Complete(matches) => /// One or more bindings have received the necessary key presses. /// Bindings added later will take precedence over earlier bindings. pub fn match_keystroke( @@ -77,12 +77,10 @@ impl KeystrokeMatcher { if let Some(pending_key) = pending_key { self.pending_keystrokes.push(pending_key); - } - - if self.pending_keystrokes.is_empty() { - KeyMatch::None - } else { KeyMatch::Pending + } else { + self.pending_keystrokes.clear(); + KeyMatch::None } } } @@ -98,367 +96,374 @@ impl KeyMatch { pub fn is_some(&self) -> bool { matches!(self, KeyMatch::Some(_)) } + + pub fn matches(self) -> Option>> { + match self { + KeyMatch::Some(matches) => Some(matches), + _ => None, + } + } +} + +impl PartialEq for KeyMatch { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (KeyMatch::None, KeyMatch::None) => true, + (KeyMatch::Pending, KeyMatch::Pending) => true, + (KeyMatch::Some(a), KeyMatch::Some(b)) => { + if a.len() != b.len() { + return false; + } + + for (a, b) in a.iter().zip(b.iter()) { + if !a.partial_eq(b.as_ref()) { + return false; + } + } + + true + } + _ => false, + } + } } -// #[cfg(test)] -// mod tests { -// use anyhow::Result; -// use serde::Deserialize; - -// use crate::{actions, impl_actions, keymap_matcher::ActionContext}; - -// use super::*; - -// #[test] -// fn test_keymap_and_view_ordering() -> Result<()> { -// actions!(test, [EditorAction, ProjectPanelAction]); - -// let mut editor = ActionContext::default(); -// editor.add_identifier("Editor"); - -// let mut project_panel = ActionContext::default(); -// project_panel.add_identifier("ProjectPanel"); - -// // Editor 'deeper' in than project panel -// let dispatch_path = vec![(2, editor), (1, project_panel)]; - -// // But editor actions 'higher' up in keymap -// let keymap = Keymap::new(vec![ -// Binding::new("left", EditorAction, Some("Editor")), -// Binding::new("left", ProjectPanelAction, Some("ProjectPanel")), -// ]); - -// let mut matcher = KeymapMatcher::new(keymap); - -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![ -// (2, Box::new(EditorAction)), -// (1, Box::new(ProjectPanelAction)), -// ]), -// ); - -// Ok(()) -// } - -// #[test] -// fn test_push_keystroke() -> Result<()> { -// actions!(test, [B, AB, C, D, DA, E, EF]); - -// let mut context1 = ActionContext::default(); -// context1.add_identifier("1"); - -// let mut context2 = ActionContext::default(); -// context2.add_identifier("2"); - -// let dispatch_path = vec![(2, context2), (1, context1)]; - -// let keymap = Keymap::new(vec![ -// Binding::new("a b", AB, Some("1")), -// Binding::new("b", B, Some("2")), -// Binding::new("c", C, Some("2")), -// Binding::new("d", D, Some("1")), -// Binding::new("d", D, Some("2")), -// Binding::new("d a", DA, Some("2")), -// ]); - -// let mut matcher = KeymapMatcher::new(keymap); - -// // Binding with pending prefix always takes precedence -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Pending, -// ); -// // B alone doesn't match because a was pending, so AB is returned instead -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(1, Box::new(AB))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); - -// // Without an a prefix, B is dispatched like expected -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(B))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); - -// // If a is prefixed, C will not be dispatched because there -// // was a pending binding for it -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Pending, -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("c")?, dispatch_path.clone()), -// KeyMatch::None, -// ); -// assert!(!matcher.has_pending_keystrokes()); - -// // If a single keystroke matches multiple bindings in the tree -// // all of them are returned so that we can fallback if the action -// // handler decides to propagate the action -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("d")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]), -// ); - -// // If none of the d action handlers consume the binding, a pending -// // binding may then be used -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), -// KeyMatch::Matches(vec![(2, Box::new(DA))]), -// ); -// assert!(!matcher.has_pending_keystrokes()); - -// Ok(()) -// } - -// #[test] -// fn test_keystroke_parsing() -> Result<()> { -// assert_eq!( -// Keystroke::parse("ctrl-p")?, -// Keystroke { -// key: "p".into(), -// ctrl: true, -// alt: false, -// shift: false, -// cmd: false, -// function: false, -// ime_key: None, -// } -// ); - -// assert_eq!( -// Keystroke::parse("alt-shift-down")?, -// Keystroke { -// key: "down".into(), -// ctrl: false, -// alt: true, -// shift: true, -// cmd: false, -// function: false, -// ime_key: None, -// } -// ); - -// assert_eq!( -// Keystroke::parse("shift-cmd--")?, -// Keystroke { -// key: "-".into(), -// ctrl: false, -// alt: false, -// shift: true, -// cmd: true, -// function: false, -// ime_key: None, -// } -// ); - -// Ok(()) -// } - -// #[test] -// fn test_context_predicate_parsing() -> Result<()> { -// use KeymapContextPredicate::*; - -// assert_eq!( -// KeymapContextPredicate::parse("a && (b == c || d != e)")?, -// And( -// Box::new(Identifier("a".into())), -// Box::new(Or( -// Box::new(Equal("b".into(), "c".into())), -// Box::new(NotEqual("d".into(), "e".into())), -// )) -// ) -// ); - -// assert_eq!( -// KeymapContextPredicate::parse("!a")?, -// Not(Box::new(Identifier("a".into())),) -// ); - -// Ok(()) -// } - -// #[test] -// fn test_context_predicate_eval() { -// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); - -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// assert!(!predicate.eval(&[context])); - -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_identifier("b"); -// assert!(predicate.eval(&[context])); - -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_key("c", "x"); -// assert!(!predicate.eval(&[context])); - -// let mut context = ActionContext::default(); -// context.add_identifier("a"); -// context.add_key("c", "d"); -// assert!(predicate.eval(&[context])); - -// let predicate = KeymapContextPredicate::parse("!a").unwrap(); -// assert!(predicate.eval(&[ActionContext::default()])); -// } - -// #[test] -// fn test_context_child_predicate_eval() { -// let predicate = KeymapContextPredicate::parse("a && b > c").unwrap(); -// let contexts = [ -// context_set(&["e", "f"]), -// context_set(&["c", "d"]), // match this context -// context_set(&["a", "b"]), -// ]; - -// assert!(!predicate.eval(&contexts[0..])); -// assert!(predicate.eval(&contexts[1..])); -// assert!(!predicate.eval(&contexts[2..])); - -// let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap(); -// let contexts = [ -// context_set(&["f"]), -// context_set(&["e"]), // only match this context -// context_set(&["c"]), -// context_set(&["a", "b"]), -// context_set(&["e"]), -// context_set(&["c", "d"]), -// context_set(&["a", "b"]), -// ]; - -// assert!(!predicate.eval(&contexts[0..])); -// assert!(predicate.eval(&contexts[1..])); -// assert!(!predicate.eval(&contexts[2..])); -// assert!(!predicate.eval(&contexts[3..])); -// assert!(!predicate.eval(&contexts[4..])); -// assert!(!predicate.eval(&contexts[5..])); -// assert!(!predicate.eval(&contexts[6..])); - -// fn context_set(names: &[&str]) -> ActionContext { -// let mut keymap = ActionContext::new(); -// names -// .iter() -// .for_each(|name| keymap.add_identifier(name.to_string())); -// keymap -// } -// } - -// #[test] -// fn test_matcher() -> Result<()> { -// #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] -// pub struct A(pub String); -// impl_actions!(test, [A]); -// actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]); - -// #[derive(Clone, Debug, Eq, PartialEq)] -// struct ActionArg { -// a: &'static str, -// } - -// let keymap = Keymap::new(vec![ -// Binding::new("a", A("x".to_string()), Some("a")), -// Binding::new("b", B, Some("a")), -// Binding::new("a b", Ab, Some("a || b")), -// Binding::new("$", Dollar, Some("a")), -// Binding::new("\"", Quote, Some("a")), -// Binding::new("alt-s", Ess, Some("a")), -// Binding::new("ctrl-`", Backtick, Some("a")), -// ]); - -// let mut context_a = ActionContext::default(); -// context_a.add_identifier("a"); - -// let mut context_b = ActionContext::default(); -// context_b.add_identifier("b"); - -// let mut matcher = KeymapMatcher::new(keymap); - -// // Basic match -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))]) -// ); -// matcher.clear_pending(); - -// // Multi-keystroke match -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ab))]) -// ); -// matcher.clear_pending(); - -// // Failed matches don't interfere with matching subsequent keys -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]), -// KeyMatch::None -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))]) -// ); -// matcher.clear_pending(); - -// // Pending keystrokes are cleared when the context changes -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]), -// KeyMatch::None -// ); -// matcher.clear_pending(); - -// let mut context_c = ActionContext::default(); -// context_c.add_identifier("c"); - -// // Pending keystrokes are maintained per-view -// assert_eq!( -// matcher.match_keystroke( -// Keystroke::parse("a")?, -// vec![(1, context_b.clone()), (2, context_c.clone())] -// ), -// KeyMatch::Pending -// ); -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ab))]) -// ); - -// // handle Czech $ (option + 4 key) -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Dollar))]) -// ); - -// // handle Brazillian quote (quote key then space key) -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Quote))]) -// ); - -// // handle ctrl+` on a brazillian keyboard -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Backtick))]) -// ); - -// // handle alt-s on a US keyboard -// assert_eq!( -// matcher.match_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]), -// KeyMatch::Matches(vec![(1, Box::new(Ess))]) -// ); - -// Ok(()) -// } -// } +#[cfg(test)] +mod tests { + + use serde_derive::Deserialize; + + use super::*; + use crate::{self as gpui, KeyBindingContextPredicate, Modifiers}; + use crate::{actions, KeyBinding}; + + #[test] + fn test_keymap_and_view_ordering() { + actions!(test, [EditorAction, ProjectPanelAction]); + + let mut editor = KeyContext::default(); + editor.add("Editor"); + + let mut project_panel = KeyContext::default(); + project_panel.add("ProjectPanel"); + + // Editor 'deeper' in than project panel + let dispatch_path = vec![project_panel, editor]; + + // But editor actions 'higher' up in keymap + let keymap = Keymap::new(vec![ + KeyBinding::new("left", EditorAction, Some("Editor")), + KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")), + ]); + + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); + + let matches = matcher + .match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path) + .matches() + .unwrap(); + + assert!(matches[0].partial_eq(&EditorAction)); + assert!(matches.get(1).is_none()); + } + + #[test] + fn test_multi_keystroke_match() { + actions!(test, [B, AB, C, D, DA, E, EF]); + + let mut context1 = KeyContext::default(); + context1.add("1"); + + let mut context2 = KeyContext::default(); + context2.add("2"); + + let dispatch_path = vec![context2, context1]; + + let keymap = Keymap::new(vec![ + KeyBinding::new("a b", AB, Some("1")), + KeyBinding::new("b", B, Some("2")), + KeyBinding::new("c", C, Some("2")), + KeyBinding::new("d", D, Some("1")), + KeyBinding::new("d", D, Some("2")), + KeyBinding::new("d a", DA, Some("2")), + ]); + + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); + + // Binding with pending prefix always takes precedence + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path), + KeyMatch::Pending, + ); + // B alone doesn't match because a was pending, so AB is returned instead + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path), + KeyMatch::Some(vec![Box::new(AB)]), + ); + assert!(!matcher.has_pending_keystrokes()); + + // Without an a prefix, B is dispatched like expected + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path[0..1]), + KeyMatch::Some(vec![Box::new(B)]), + ); + assert!(!matcher.has_pending_keystrokes()); + + eprintln!("PROBLEM AREA"); + // If a is prefixed, C will not be dispatched because there + // was a pending binding for it + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path), + KeyMatch::Pending, + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path), + KeyMatch::None, + ); + assert!(!matcher.has_pending_keystrokes()); + + // If a single keystroke matches multiple bindings in the tree + // only one of them is returned. + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path), + KeyMatch::Some(vec![Box::new(D)]), + ); + } + + #[test] + fn test_keystroke_parsing() { + assert_eq!( + Keystroke::parse("ctrl-p").unwrap(), + Keystroke { + key: "p".into(), + modifiers: Modifiers { + control: true, + alt: false, + shift: false, + command: false, + function: false, + }, + ime_key: None, + } + ); + + assert_eq!( + Keystroke::parse("alt-shift-down").unwrap(), + Keystroke { + key: "down".into(), + modifiers: Modifiers { + control: false, + alt: true, + shift: true, + command: false, + function: false, + }, + ime_key: None, + } + ); + + assert_eq!( + Keystroke::parse("shift-cmd--").unwrap(), + Keystroke { + key: "-".into(), + modifiers: Modifiers { + control: false, + alt: false, + shift: true, + command: true, + function: false, + }, + ime_key: None, + } + ); + } + + #[test] + fn test_context_predicate_parsing() { + use KeyBindingContextPredicate::*; + + assert_eq!( + KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(), + And( + Box::new(Identifier("a".into())), + Box::new(Or( + Box::new(Equal("b".into(), "c".into())), + Box::new(NotEqual("d".into(), "e".into())), + )) + ) + ); + + assert_eq!( + KeyBindingContextPredicate::parse("!a").unwrap(), + Not(Box::new(Identifier("a".into())),) + ); + } + + #[test] + fn test_context_predicate_eval() { + let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap(); + + let mut context = KeyContext::default(); + context.add("a"); + assert!(!predicate.eval(&[context])); + + let mut context = KeyContext::default(); + context.add("a"); + context.add("b"); + assert!(predicate.eval(&[context])); + + let mut context = KeyContext::default(); + context.add("a"); + context.set("c", "x"); + assert!(!predicate.eval(&[context])); + + let mut context = KeyContext::default(); + context.add("a"); + context.set("c", "d"); + assert!(predicate.eval(&[context])); + + let predicate = KeyBindingContextPredicate::parse("!a").unwrap(); + assert!(predicate.eval(&[KeyContext::default()])); + } + + #[test] + fn test_context_child_predicate_eval() { + let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap(); + let contexts = [ + context_set(&["e", "f"]), + context_set(&["c", "d"]), // match this context + context_set(&["a", "b"]), + ]; + + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + + let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap(); + let contexts = [ + context_set(&["f"]), + context_set(&["e"]), // only match this context + context_set(&["c"]), + context_set(&["a", "b"]), + context_set(&["e"]), + context_set(&["c", "d"]), + context_set(&["a", "b"]), + ]; + + assert!(!predicate.eval(&contexts[0..])); + assert!(predicate.eval(&contexts[1..])); + assert!(!predicate.eval(&contexts[2..])); + assert!(!predicate.eval(&contexts[3..])); + assert!(!predicate.eval(&contexts[4..])); + assert!(!predicate.eval(&contexts[5..])); + assert!(!predicate.eval(&contexts[6..])); + + fn context_set(names: &[&str]) -> KeyContext { + let mut keymap = KeyContext::default(); + names.iter().for_each(|name| keymap.add(name.to_string())); + keymap + } + } + + #[test] + fn test_matcher() { + #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] + pub struct A(pub String); + impl_actions!(test, [A]); + actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]); + + #[derive(Clone, Debug, Eq, PartialEq)] + struct ActionArg { + a: &'static str, + } + + let keymap = Keymap::new(vec![ + KeyBinding::new("a", A("x".to_string()), Some("a")), + KeyBinding::new("b", B, Some("a")), + KeyBinding::new("a b", Ab, Some("a || b")), + KeyBinding::new("$", Dollar, Some("a")), + KeyBinding::new("\"", Quote, Some("a")), + KeyBinding::new("alt-s", Ess, Some("a")), + KeyBinding::new("ctrl-`", Backtick, Some("a")), + ]); + + let mut context_a = KeyContext::default(); + context_a.add("a"); + + let mut context_b = KeyContext::default(); + context_b.add("b"); + + let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); + + // Basic match + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(A("x".to_string()))]) + ); + matcher.clear_pending(); + + // Multi-keystroke match + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]), + KeyMatch::Pending + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]), + KeyMatch::Some(vec![Box::new(Ab)]) + ); + matcher.clear_pending(); + + // Failed matches don't interfere with matching subsequent keys + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]), + KeyMatch::None + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(A("x".to_string()))]) + ); + matcher.clear_pending(); + + let mut context_c = KeyContext::default(); + context_c.add("c"); + + assert_eq!( + matcher.match_keystroke( + &Keystroke::parse("a").unwrap(), + &[context_c.clone(), context_b.clone()] + ), + KeyMatch::Pending + ); + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]), + KeyMatch::Some(vec![Box::new(Ab)]) + ); + + // handle Czech $ (option + 4 key) + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("alt-ç->$").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Dollar)]) + ); + + // handle Brazillian quote (quote key then space key) + assert_eq!( + matcher.match_keystroke( + &Keystroke::parse("space->\"").unwrap(), + &[context_a.clone()] + ), + KeyMatch::Some(vec![Box::new(Quote)]) + ); + + // handle ctrl+` on a brazillian keyboard + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Backtick)]) + ); + + // handle alt-s on a US keyboard + assert_eq!( + matcher.match_keystroke(&Keystroke::parse("alt-s->ß").unwrap(), &[context_a.clone()]), + KeyMatch::Some(vec![Box::new(Ess)]) + ); + } +} diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index d9f7936066b248a7037fb2ea810b7c4a5dc431d2..79ffb8dc8e4aa54b954494a8cae4f3ca6186b377 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -592,169 +592,49 @@ impl From for FontkitStyle { } } -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::AppContext; -// use font_kit::properties::{Style, Weight}; -// use platform::FontSystem as _; - -// #[crate::test(self, retries = 5)] -// fn test_layout_str(_: &mut AppContext) { -// // This is failing intermittently on CI and we don't have time to figure it out -// let fonts = FontSystem::new(); -// let menlo = fonts.load_family("Menlo", &Default::default()).unwrap(); -// let menlo_regular = RunStyle { -// font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo_italic = RunStyle { -// font_id: fonts -// .select_font(&menlo, Properties::new().style(Style::Italic)) -// .unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo_bold = RunStyle { -// font_id: fonts -// .select_font(&menlo, Properties::new().weight(Weight::BOLD)) -// .unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; -// assert_ne!(menlo_regular, menlo_italic); -// assert_ne!(menlo_regular, menlo_bold); -// assert_ne!(menlo_italic, menlo_bold); - -// let line = fonts.layout_line( -// "hello world", -// 16.0, -// &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)], -// ); -// assert_eq!(line.runs.len(), 3); -// assert_eq!(line.runs[0].font_id, menlo_bold.font_id); -// assert_eq!(line.runs[0].glyphs.len(), 2); -// assert_eq!(line.runs[1].font_id, menlo_italic.font_id); -// assert_eq!(line.runs[1].glyphs.len(), 4); -// assert_eq!(line.runs[2].font_id, menlo_regular.font_id); -// assert_eq!(line.runs[2].glyphs.len(), 5); -// } - -// #[test] -// fn test_glyph_offsets() -> crate::Result<()> { -// let fonts = FontSystem::new(); -// let zapfino = fonts.load_family("Zapfino", &Default::default())?; -// let zapfino_regular = RunStyle { -// font_id: fonts.select_font(&zapfino, &Properties::new())?, -// color: Default::default(), -// underline: Default::default(), -// }; -// let menlo = fonts.load_family("Menlo", &Default::default())?; -// let menlo_regular = RunStyle { -// font_id: fonts.select_font(&menlo, &Properties::new())?, -// color: Default::default(), -// underline: Default::default(), -// }; - -// let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈"; -// let line = fonts.layout_line( -// text, -// 16.0, -// &[ -// (9, zapfino_regular), -// (13, menlo_regular), -// (text.len() - 22, zapfino_regular), -// ], -// ); -// assert_eq!( -// line.runs -// .iter() -// .flat_map(|r| r.glyphs.iter()) -// .map(|g| g.index) -// .collect::>(), -// vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37], -// ); -// Ok(()) -// } - -// #[test] -// #[ignore] -// fn test_rasterize_glyph() { -// use std::{fs::File, io::BufWriter, path::Path}; - -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap(); -// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); -// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap(); - -// const VARIANTS: usize = 1; -// for i in 0..VARIANTS { -// let variant = i as f32 / VARIANTS as f32; -// let (bounds, bytes) = fonts -// .rasterize_glyph( -// font_id, -// 16.0, -// glyph_id, -// vec2f(variant, variant), -// 2., -// RasterizationOptions::Alpha, -// ) -// .unwrap(); - -// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i); -// let path = Path::new(&name); -// let file = File::create(path).unwrap(); -// let w = &mut BufWriter::new(file); - -// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32); -// encoder.set_color(png::ColorType::Grayscale); -// encoder.set_depth(png::BitDepth::Eight); -// let mut writer = encoder.write_header().unwrap(); -// writer.write_image_data(&bytes).unwrap(); -// } -// } - -// #[test] -// fn test_wrap_line() { -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap(); -// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); - -// let line = "one two three four five\n"; -// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); -// assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]); - -// let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n"; -// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); -// assert_eq!( -// wrap_boundaries, -// &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] -// ); -// } - -// #[test] -// fn test_layout_line_bom_char() { -// let fonts = FontSystem::new(); -// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap(); -// let style = RunStyle { -// font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(), -// color: Default::default(), -// underline: Default::default(), -// }; - -// let line = "\u{feff}"; -// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); -// assert_eq!(layout.len, line.len()); -// assert!(layout.runs.is_empty()); - -// let line = "a\u{feff}b"; -// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); -// assert_eq!(layout.len, line.len()); -// assert_eq!(layout.runs.len(), 1); -// assert_eq!(layout.runs[0].glyphs.len(), 2); -// assert_eq!(layout.runs[0].glyphs[0].id, 68); // a -// // There's no glyph for \u{feff} -// assert_eq!(layout.runs[0].glyphs[1].id, 69); // b -// } -// } +#[cfg(test)] +mod tests { + use crate::{font, px, FontRun, MacTextSystem, PlatformTextSystem}; + + #[test] + fn test_wrap_line() { + let fonts = MacTextSystem::new(); + let font_id = fonts.font_id(&font("Helvetica")).unwrap(); + + let line = "one two three four five\n"; + let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(64.0)); + assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]); + + let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n"; + let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(64.0)); + assert_eq!( + wrap_boundaries, + &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] + ); + } + + #[test] + fn test_layout_line_bom_char() { + let fonts = MacTextSystem::new(); + let font_id = fonts.font_id(&font("Helvetica")).unwrap(); + let line = "\u{feff}"; + let mut style = FontRun { + font_id, + len: line.len(), + }; + + let layout = fonts.layout_line(line, px(16.), &[style]); + assert_eq!(layout.len, line.len()); + assert!(layout.runs.is_empty()); + + let line = "a\u{feff}b"; + style.len = line.len(); + let layout = fonts.layout_line(line, px(16.), &[style]); + assert_eq!(layout.len, line.len()); + assert_eq!(layout.runs.len(), 1); + assert_eq!(layout.runs[0].glyphs.len(), 2); + assert_eq!(layout.runs[0].glyphs[0].id, 68u32.into()); // a + // There's no glyph for \u{feff} + assert_eq!(layout.runs[0].glyphs[1].id, 69u32.into()); // b + } +} From 83163a00318123490b0738df444a410b20952ade Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 19:54:39 -0800 Subject: [PATCH 104/334] Reverse context arrays in child predicate test --- crates/gpui/src/keymap/matcher.rs | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/gpui/src/keymap/matcher.rs b/crates/gpui/src/keymap/matcher.rs index fb508766a1f4386d3b4064d373b40f3ed42ce48d..5410ddce06e9ca999aaee0911fd7a69d6a0e61c8 100644 --- a/crates/gpui/src/keymap/matcher.rs +++ b/crates/gpui/src/keymap/matcher.rs @@ -330,33 +330,33 @@ mod tests { fn test_context_child_predicate_eval() { let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap(); let contexts = [ - context_set(&["e", "f"]), - context_set(&["c", "d"]), // match this context context_set(&["a", "b"]), + context_set(&["c", "d"]), // match this context + context_set(&["e", "f"]), ]; - assert!(!predicate.eval(&contexts[0..])); - assert!(predicate.eval(&contexts[1..])); - assert!(!predicate.eval(&contexts[2..])); + assert!(!predicate.eval(&contexts[..=0])); + assert!(predicate.eval(&contexts[..=1])); + assert!(!predicate.eval(&contexts[..=2])); let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap(); let contexts = [ - context_set(&["f"]), - context_set(&["e"]), // only match this context - context_set(&["c"]), context_set(&["a", "b"]), - context_set(&["e"]), context_set(&["c", "d"]), + context_set(&["e"]), context_set(&["a", "b"]), + context_set(&["c"]), + context_set(&["e"]), // only match this context + context_set(&["f"]), ]; - assert!(!predicate.eval(&contexts[0..])); - assert!(predicate.eval(&contexts[1..])); - assert!(!predicate.eval(&contexts[2..])); - assert!(!predicate.eval(&contexts[3..])); - assert!(!predicate.eval(&contexts[4..])); - assert!(!predicate.eval(&contexts[5..])); - assert!(!predicate.eval(&contexts[6..])); + assert!(!predicate.eval(&contexts[..=0])); + assert!(!predicate.eval(&contexts[..=1])); + assert!(!predicate.eval(&contexts[..=2])); + assert!(!predicate.eval(&contexts[..=3])); + assert!(!predicate.eval(&contexts[..=4])); + assert!(predicate.eval(&contexts[..=5])); + assert!(!predicate.eval(&contexts[..=6])); fn context_set(names: &[&str]) -> KeyContext { let mut keymap = KeyContext::default(); From f418bd907da5e0b91deae6e3c663de638db30412 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 10 Jan 2024 23:02:36 -0700 Subject: [PATCH 105/334] Stop following when project is unshared Before this change the views would continue to update in the background of the "disconnected" dialogue, which was disconcerting. --- crates/call/src/room.rs | 7 +- .../collab/src/tests/channel_guest_tests.rs | 10 +-- crates/collab/src/tests/following_tests.rs | 58 ++++++++++++- crates/collab/src/tests/test_server.rs | 64 ++++++++++++++- crates/project/src/project.rs | 82 ++++++++++--------- crates/vim/src/editor_events.rs | 1 - crates/workspace/src/workspace.rs | 5 ++ 7 files changed, 174 insertions(+), 53 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 04e883e68656a5c983af0f4f85f219b389c95567..45c6c15fb00a4cc42131d7b3acfa8524201044c1 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1225,7 +1225,12 @@ impl Room { }; self.client.send(proto::UnshareProject { project_id })?; - project.update(cx, |this, cx| this.unshare(cx)) + project.update(cx, |this, cx| this.unshare(cx))?; + + if self.local_participant.active_project == Some(project.downgrade()) { + self.set_location(Some(&project), cx).detach_and_log_err(cx); + } + Ok(()) } pub(crate) fn set_location( diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 9b68ce3922ab24726130c356636dae7d6899ef35..d5933235926fa1da8c1e8538dedd2a7506504cd8 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -104,12 +104,10 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); - assert!(dbg!( - room_b - .update(cx_b, |room, cx| room.share_microphone(cx)) - .await - ) - .is_err()); + assert!(room_b + .update(cx_b, |room, cx| room.share_microphone(cx)) + .await + .is_err()); // B is promoted active_call_a diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 9209760353935bfdd3573317a9cd3d0ca4e85573..6106f8d5f1531302374de4456bf9fd9aff11b871 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1,5 +1,5 @@ use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; -use call::ActiveCall; +use call::{ActiveCall, ParticipantLocation}; use collab_ui::notifications::project_shared_notification::ProjectSharedNotification; use editor::{Editor, ExcerptRange, MultiBuffer}; use gpui::{ @@ -1568,6 +1568,59 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut }); } +#[gpui::test] +async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let (client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await; + + let (workspace_a, cx_a) = client_a.build_test_workspace(cx_a).await; + client_a + .host_workspace(&workspace_a, channel_id, cx_a) + .await; + let (workspace_b, cx_b) = client_b.join_workspace(channel_id, cx_b).await; + + cx_a.simulate_keystrokes("cmd-p 2 enter"); + cx_a.run_until_parked(); + + let editor_a = workspace_a.update(cx_a, |workspace, cx| { + workspace.active_item_as::(cx).unwrap() + }); + let editor_b = workspace_b.update(cx_b, |workspace, cx| { + workspace.active_item_as::(cx).unwrap() + }); + + // b should follow a to position 1 + editor_a.update(cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([1..1])) + }); + cx_a.run_until_parked(); + editor_b.update(cx_b, |editor, cx| { + assert_eq!(editor.selections.ranges(cx), vec![1..1]) + }); + + // a unshares the project + cx_a.update(|cx| { + let project = workspace_a.read(cx).project().clone(); + ActiveCall::global(cx).update(cx, |call, cx| { + call.unshare_project(project, cx).unwrap(); + }) + }); + cx_a.run_until_parked(); + + // b should not follow a to position 2 + editor_a.update(cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([2..2])) + }); + cx_a.run_until_parked(); + editor_b.update(cx_b, |editor, cx| { + assert_eq!(editor.selections.ranges(cx), vec![1..1]) + }); + cx_b.update(|cx| { + let room = ActiveCall::global(cx).read(cx).room().unwrap().read(cx); + let participant = room.remote_participants().get(&client_a.id()).unwrap(); + assert_eq!(participant.location, ParticipantLocation::UnsharedProject) + }) +} + #[gpui::test] async fn test_following_into_excluded_file( mut cx_a: &mut TestAppContext, @@ -1593,9 +1646,6 @@ async fn test_following_into_excluded_file( let active_call_b = cx_b.read(ActiveCall::global); let peer_id_a = client_a.peer_id().unwrap(); - cx_a.update(editor::init); - cx_b.update(editor::init); - client_a .fs() .insert_tree( diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 2b2bb3a6a42b25e35d1e8763bd58bf6d8c7609fd..4fcf6aa67652fabe9187ba5f78b8899379dc471b 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -113,6 +113,20 @@ impl TestServer { } } + pub async fn start2( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + ) -> (TestClient, TestClient, u64) { + let mut server = Self::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let channel_id = server + .make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)]) + .await; + + (client_a, client_b, channel_id) + } + pub async fn reset(&self) { self.app_state.db.reset(); let epoch = self @@ -619,14 +633,49 @@ impl TestClient { "/a", json!({ "1.txt": "one\none\none", - "2.txt": "two\ntwo\ntwo", - "3.txt": "three\nthree\nthree", + "2.js": "function two() { return 2; }", + "3.rs": "mod test", }), ) .await; self.build_local_project("/a", cx).await.0 } + pub async fn host_workspace( + &self, + workspace: &View, + channel_id: u64, + cx: &mut VisualTestContext, + ) { + cx.update(|cx| { + let active_call = ActiveCall::global(cx); + active_call.update(cx, |call, cx| call.join_channel(channel_id, cx)) + }) + .await + .unwrap(); + cx.update(|cx| { + let active_call = ActiveCall::global(cx); + let project = workspace.read(cx).project().clone(); + active_call.update(cx, |call, cx| call.share_project(project, cx)) + }) + .await + .unwrap(); + cx.executor().run_until_parked(); + } + + pub async fn join_workspace<'a>( + &'a self, + channel_id: u64, + cx: &'a mut TestAppContext, + ) -> (View, &'a mut VisualTestContext) { + cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx)) + .await + .unwrap(); + cx.run_until_parked(); + + self.active_workspace(cx) + } + pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model { cx.update(|cx| { Project::local( @@ -670,6 +719,17 @@ impl TestClient { }) } + pub async fn build_test_workspace<'a>( + &'a self, + cx: &'a mut TestAppContext, + ) -> (View, &'a mut VisualTestContext) { + let project = self.build_test_project(cx).await; + cx.add_window_view(|cx| { + cx.activate_window(); + Workspace::new(0, project.clone(), self.app_state.clone(), cx) + }) + } + pub fn active_workspace<'a>( &'a self, cx: &'a mut TestAppContext, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c412fad0e10ba07ee4849273577d6b2425089820..06b6da75b3ed382d03becbb5a418ddf28cbc05c0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -130,7 +130,7 @@ pub struct Project { next_diagnostic_group_id: usize, user_store: Model, fs: Arc, - client_state: Option, + client_state: ProjectClientState, collaborators: HashMap, client_subscriptions: Vec, _subscriptions: Vec, @@ -254,8 +254,10 @@ enum WorktreeHandle { Weak(WeakModel), } +#[derive(Debug)] enum ProjectClientState { - Local { + Local, + Shared { remote_id: u64, updates_tx: mpsc::UnboundedSender, _send_updates: Task>, @@ -657,7 +659,7 @@ impl Project { local_buffer_ids_by_entry_id: Default::default(), buffer_snapshots: Default::default(), join_project_response_message_id: 0, - client_state: None, + client_state: ProjectClientState::Local, opened_buffer: watch::channel(), client_subscriptions: Vec::new(), _subscriptions: vec![ @@ -756,12 +758,12 @@ impl Project { cx.on_app_quit(Self::shutdown_language_servers), ], client: client.clone(), - client_state: Some(ProjectClientState::Remote { + client_state: ProjectClientState::Remote { sharing_has_stopped: false, capability: Capability::ReadWrite, remote_id, replica_id, - }), + }, supplementary_language_servers: HashMap::default(), language_servers: Default::default(), language_server_ids: Default::default(), @@ -828,16 +830,16 @@ impl Project { fn release(&mut self, cx: &mut AppContext) { match &self.client_state { - Some(ProjectClientState::Local { .. }) => { + ProjectClientState::Local => {} + ProjectClientState::Shared { .. } => { let _ = self.unshare_internal(cx); } - Some(ProjectClientState::Remote { remote_id, .. }) => { + ProjectClientState::Remote { remote_id, .. } => { let _ = self.client.send(proto::LeaveProject { project_id: *remote_id, }); self.disconnected_from_host_internal(cx); } - _ => {} } } @@ -1058,21 +1060,22 @@ impl Project { } pub fn remote_id(&self) -> Option { - match self.client_state.as_ref()? { - ProjectClientState::Local { remote_id, .. } - | ProjectClientState::Remote { remote_id, .. } => Some(*remote_id), + match self.client_state { + ProjectClientState::Local => None, + ProjectClientState::Shared { remote_id, .. } + | ProjectClientState::Remote { remote_id, .. } => Some(remote_id), } } pub fn replica_id(&self) -> ReplicaId { - match &self.client_state { - Some(ProjectClientState::Remote { replica_id, .. }) => *replica_id, + match self.client_state { + ProjectClientState::Remote { replica_id, .. } => replica_id, _ => 0, } } fn metadata_changed(&mut self, cx: &mut ModelContext) { - if let Some(ProjectClientState::Local { updates_tx, .. }) = &mut self.client_state { + if let ProjectClientState::Shared { updates_tx, .. } = &mut self.client_state { updates_tx .unbounded_send(LocalProjectUpdate::WorktreesChanged) .ok(); @@ -1362,7 +1365,7 @@ impl Project { } pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext) -> Result<()> { - if self.client_state.is_some() { + if !matches!(self.client_state, ProjectClientState::Local) { return Err(anyhow!("project was already shared")); } self.client_subscriptions.push( @@ -1423,7 +1426,7 @@ impl Project { let (updates_tx, mut updates_rx) = mpsc::unbounded(); let client = self.client.clone(); - self.client_state = Some(ProjectClientState::Local { + self.client_state = ProjectClientState::Shared { remote_id: project_id, updates_tx, _send_updates: cx.spawn(move |this, mut cx| async move { @@ -1508,7 +1511,7 @@ impl Project { } Ok(()) }), - }); + }; self.metadata_changed(cx); cx.emit(Event::RemoteIdChanged(Some(project_id))); @@ -1578,7 +1581,8 @@ impl Project { return Err(anyhow!("attempted to unshare a remote project")); } - if let Some(ProjectClientState::Local { remote_id, .. }) = self.client_state.take() { + if let ProjectClientState::Shared { remote_id, .. } = self.client_state { + self.client_state = ProjectClientState::Local; self.collaborators.clear(); self.shared_buffers.clear(); self.client_subscriptions.clear(); @@ -1629,23 +1633,23 @@ impl Project { } else { Capability::ReadOnly }; - if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { + if let ProjectClientState::Remote { capability, .. } = &mut self.client_state { if *capability == new_capability { return; } *capability = new_capability; - } - for buffer in self.opened_buffers() { - buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); + for buffer in self.opened_buffers() { + buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); + } } } fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) { - if let Some(ProjectClientState::Remote { + if let ProjectClientState::Remote { sharing_has_stopped, .. - }) = &mut self.client_state + } = &mut self.client_state { *sharing_has_stopped = true; @@ -1684,18 +1688,18 @@ impl Project { pub fn is_disconnected(&self) -> bool { match &self.client_state { - Some(ProjectClientState::Remote { + ProjectClientState::Remote { sharing_has_stopped, .. - }) => *sharing_has_stopped, + } => *sharing_has_stopped, _ => false, } } pub fn capability(&self) -> Capability { match &self.client_state { - Some(ProjectClientState::Remote { capability, .. }) => *capability, - Some(ProjectClientState::Local { .. }) | None => Capability::ReadWrite, + ProjectClientState::Remote { capability, .. } => *capability, + ProjectClientState::Shared { .. } | ProjectClientState::Local => Capability::ReadWrite, } } @@ -1705,8 +1709,8 @@ impl Project { pub fn is_local(&self) -> bool { match &self.client_state { - Some(ProjectClientState::Remote { .. }) => false, - _ => true, + ProjectClientState::Local | ProjectClientState::Shared { .. } => true, + ProjectClientState::Remote { .. } => false, } } @@ -6165,8 +6169,8 @@ impl Project { pub fn is_shared(&self) -> bool { match &self.client_state { - Some(ProjectClientState::Local { .. }) => true, - _ => false, + ProjectClientState::Shared { .. } => true, + ProjectClientState::Local | ProjectClientState::Remote { .. } => false, } } @@ -7954,7 +7958,7 @@ impl Project { cx: &mut AppContext, ) -> u64 { let buffer_id = buffer.read(cx).remote_id(); - if let Some(ProjectClientState::Local { updates_tx, .. }) = &self.client_state { + if let ProjectClientState::Shared { updates_tx, .. } = &self.client_state { updates_tx .unbounded_send(LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id }) .ok(); @@ -8003,21 +8007,21 @@ impl Project { } fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext) -> Task> { - let project_id = match self.client_state.as_ref() { - Some(ProjectClientState::Remote { + let project_id = match self.client_state { + ProjectClientState::Remote { sharing_has_stopped, remote_id, .. - }) => { - if *sharing_has_stopped { + } => { + if sharing_has_stopped { return Task::ready(Err(anyhow!( "can't synchronize remote buffers on a readonly project" ))); } else { - *remote_id + remote_id } } - Some(ProjectClientState::Local { .. }) | None => { + ProjectClientState::Shared { .. } | ProjectClientState::Local => { return Task::ready(Err(anyhow!( "can't synchronize remote buffers on a local project" ))) diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index e3ed076698d101a12f92c16048748532ea596e1c..e4057792796cad86d7fe31ff01b78ea9434ae102 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -111,7 +111,6 @@ mod test { let mut cx1 = VisualTestContext::from_window(cx.window, &cx); let editor1 = cx.editor.clone(); - dbg!(editor1.entity_id()); let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 839edf1009ab4c9397b16c9f9959bab89f8c169c..47432b1f3630f8cb4d4d7f02e2ba32efaca01266 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -512,6 +512,11 @@ impl Workspace { project::Event::DisconnectedFromHost => { this.update_window_edited(cx); + let panes_to_unfollow: Vec> = + this.follower_states.keys().map(|k| k.clone()).collect(); + for pane in panes_to_unfollow { + this.unfollow(&pane, cx); + } cx.disable_focus(); } From 8a61d5059b493685bf60f37765d916328acde846 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 01:12:30 -0500 Subject: [PATCH 106/334] Never send an an empty set of events --- crates/client/src/telemetry.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 628b5292c63c7f895e771c70a425ef7a44151f16..2bfc20898a7a006509d5e7821949fb47fd20b6cc 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -461,6 +461,9 @@ impl Telemetry { let mut events = mem::take(&mut state.events_queue); state.flush_events_task.take(); drop(state); + if events.is_empty() { + return; + } let this = self.clone(); self.executor From 0df4bfacc23b25467aac60d3bbc65dbfb5ab0c37 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 01:12:49 -0500 Subject: [PATCH 107/334] Increase debug mode queue size --- crates/client/src/telemetry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 2bfc20898a7a006509d5e7821949fb47fd20b6cc..32ebaad3bdbadccd51a2c0520307898cd729cadd 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -130,7 +130,7 @@ pub enum Event { } #[cfg(debug_assertions)] -const MAX_QUEUE_LEN: usize = 1; +const MAX_QUEUE_LEN: usize = 5; #[cfg(not(debug_assertions))] const MAX_QUEUE_LEN: usize = 50; From 6503dd51ddd9a16a2ee340d4ffabe4b034966c68 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 10 Jan 2024 22:57:58 -0800 Subject: [PATCH 108/334] enviroment -> environment --- .../20221109000000_test_schema.sql | 2 +- ...085546_move_channel_paths_to_channels_table.sql | 2 ++ crates/collab/src/db/queries/channels.rs | 4 ++-- crates/collab/src/db/queries/rooms.rs | 14 +++++++------- crates/collab/src/db/tables/room.rs | 2 +- docs/old/tools.md | 2 +- 6 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 9bbbf88dac9879bf12dee3c99c35c8b18ca8d527..507cf197f70b9bcce2c74a16b1c6ead569965a5d 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -37,7 +37,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); CREATE TABLE "rooms" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "live_kit_room" VARCHAR NOT NULL, - "enviroment" VARCHAR, + "environment" VARCHAR, "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE ); CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); diff --git a/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql b/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql new file mode 100644 index 0000000000000000000000000000000000000000..a737c6c273ab494b6e6236feebfab52cf809f4e3 --- /dev/null +++ b/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql @@ -0,0 +1,2 @@ +ALTER TABLE table_name +RENAME COLUMN enviroment TO environment; \ No newline at end of file diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 9c28e998c95426bc1026bcc5df86e8c528c4da8b..6243b03bf7ad2f331b3ede34c2390db574940546 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -1180,7 +1180,7 @@ impl Database { .await?; let room_id = if let Some(room) = room { - if let Some(env) = room.enviroment { + if let Some(env) = room.environment { if &env != environment { Err(anyhow!("must join using the {} release", env))?; } @@ -1190,7 +1190,7 @@ impl Database { let result = room::Entity::insert(room::ActiveModel { channel_id: ActiveValue::Set(Some(channel_id)), live_kit_room: ActiveValue::Set(live_kit_room.to_string()), - enviroment: ActiveValue::Set(Some(environment.to_string())), + environment: ActiveValue::Set(Some(environment.to_string())), ..Default::default() }) .exec(&*tx) diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index c3e71880fdc5e8eb871dfd9a9aff3e85251a321e..178cb712ed5df6f0bcaaed45e5fd4d43996d88b1 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -112,7 +112,7 @@ impl Database { self.transaction(|tx| async move { let room = room::ActiveModel { live_kit_room: ActiveValue::set(live_kit_room.into()), - enviroment: ActiveValue::set(Some(release_channel.to_string())), + environment: ActiveValue::set(Some(release_channel.to_string())), ..Default::default() } .insert(&*tx) @@ -299,28 +299,28 @@ impl Database { room_id: RoomId, user_id: UserId, connection: ConnectionId, - enviroment: &str, + environment: &str, ) -> Result> { self.room_transaction(room_id, |tx| async move { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] - enum QueryChannelIdAndEnviroment { + enum QueryChannelIdAndEnvironment { ChannelId, - Enviroment, + Environment, } let (channel_id, release_channel): (Option, Option) = room::Entity::find() .select_only() .column(room::Column::ChannelId) - .column(room::Column::Enviroment) + .column(room::Column::Environment) .filter(room::Column::Id.eq(room_id)) - .into_values::<_, QueryChannelIdAndEnviroment>() + .into_values::<_, QueryChannelIdAndEnvironment>() .one(&*tx) .await? .ok_or_else(|| anyhow!("no such room"))?; if let Some(release_channel) = release_channel { - if &release_channel != enviroment { + if &release_channel != environment { Err(anyhow!("must join using the {} release", release_channel))?; } } diff --git a/crates/collab/src/db/tables/room.rs b/crates/collab/src/db/tables/room.rs index 4150c741ac19ef39e09c19116ba3bca819e24a3f..f75a079317311f3595279d46b41234103afc1294 100644 --- a/crates/collab/src/db/tables/room.rs +++ b/crates/collab/src/db/tables/room.rs @@ -8,7 +8,7 @@ pub struct Model { pub id: RoomId, pub live_kit_room: String, pub channel_id: Option, - pub enviroment: Option, + pub environment: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/docs/old/tools.md b/docs/old/tools.md index 22810e3e07909c6d0f42bdd4c2336d8cd438bf7a..56c3c0c963278eb2b757d642e7854391621d20f3 100644 --- a/docs/old/tools.md +++ b/docs/old/tools.md @@ -56,7 +56,7 @@ We use Vercel for all of our web deployments and some backend things. If you sig ### Environment Variables -You can get access to many of our shared enviroment variables through 1Password and Vercel. For 1Password search the value you are looking for, or sort by passwords or API credentials. +You can get access to many of our shared environment variables through 1Password and Vercel. For 1Password search the value you are looking for, or sort by passwords or API credentials. For Vercel, go to `settings` -> `Environment Variables` (either on the entire org, or on a specific project depending on where it is shared.) For a given Vercel project if you have their CLI installed you can use `vercel pull` or `vercel env` to pull values down directly. More on those in their [CLI docs](https://vercel.com/docs/cli/env). From 0db7559e964a1e67f0c46ff4975ac95f7ebea36f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:26:12 +0100 Subject: [PATCH 109/334] editor: extend diff hunk range for custom transform blocks. (#4012) Reported by Mikayla: ![image](https://github.com/zed-industries/zed/assets/24362066/b744d82e-328f-4554-becf-96f9fa92bfc8) Note how the line with rust analyzer error does not have a git diff hunk. vs: ![image](https://github.com/zed-industries/zed/assets/24362066/e285af7a-b8ab-40e9-a9c6-b4ab8d6c4cd0) Release Notes: - N/A --- crates/editor/src/element.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1791fcc29277ded834c10cabf5513cd65f2db3eb..4a648b37709fe62c5e2deef0432ccdd9dbf1d0f4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -809,13 +809,18 @@ impl EditorElement { // the hunk might include the rows of that header. // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap. // Instead, we simply check whether the range we're dealing with includes - // any custom elements and if so, we stop painting the diff hunk on the first row of that custom element. + // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header. let end_row_in_current_excerpt = layout .position_map .snapshot .blocks_in_range(start_row..end_row) - .next() - .map(|(start_row, _)| start_row) + .find_map(|(start_row, block)| { + if matches!(block, TransformBlock::ExcerptHeader { .. }) { + Some(start_row) + } else { + None + } + }) .unwrap_or(end_row); let start_y = start_row as f32 * line_height - scroll_top; From 142a8b68c856264773f9cb593a155de378db87df Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 12:28:48 +0100 Subject: [PATCH 110/334] Avoid casting view ids to u32 Also, it looks like using a u64 directly doesn't work well with Metal shaders, so we unpack the u64 into two u32s. --- crates/editor/src/element.rs | 10 +++---- crates/gpui/src/scene.rs | 58 +++++++++++++++++++++++++----------- crates/gpui/src/window.rs | 18 +++++------ 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7efb43bd4852fc7ef1d8e5a5a43a79ad72a0303e..53578392e8cf2c95c3415f5e5e3de950c5db4abc 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -26,11 +26,11 @@ use git::diff::DiffHunkStatus; use gpui::{ div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, - CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, - InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, - SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, - TextStyle, View, ViewContext, WindowContext, + CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, + InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, + ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, + Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 4b716413f50b8c0458023ce2cea926cb206376fa..4179a1659e768c3967cbd18df030db0779535f79 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -13,6 +13,30 @@ pub(crate) type PathVertex_ScaledPixels = PathVertex; pub type LayerId = u32; pub type DrawOrder = u32; +#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[repr(C)] +pub struct ViewId { + low_bits: u32, + high_bits: u32, +} + +impl From for ViewId { + fn from(value: EntityId) -> Self { + let value = value.as_u64(); + Self { + low_bits: value as u32, + high_bits: (value >> 32) as u32, + } + } +} + +impl From for EntityId { + fn from(value: ViewId) -> Self { + let value = (value.low_bits as u64) | ((value.high_bits as u64) << 32); + value.into() + } +} + #[derive(Default)] pub struct Scene { layers_by_order: BTreeMap, @@ -127,49 +151,49 @@ impl Scene { pub fn reuse_views(&mut self, views: &FxHashSet, prev_scene: &mut Self) { for shadow in prev_scene.shadows.drain(..) { - if views.contains(&EntityId::from(shadow.view_id as u64)) { + if views.contains(&shadow.view_id.into()) { let order = &prev_scene.orders_by_layer[&shadow.layer_id]; self.insert(&order, shadow); } } for quad in prev_scene.quads.drain(..) { - if views.contains(&EntityId::from(quad.view_id as u64)) { + if views.contains(&quad.view_id.into()) { let order = &prev_scene.orders_by_layer[&quad.layer_id]; self.insert(&order, quad); } } for path in prev_scene.paths.drain(..) { - if views.contains(&EntityId::from(path.view_id as u64)) { + if views.contains(&path.view_id.into()) { let order = &prev_scene.orders_by_layer[&path.layer_id]; self.insert(&order, path); } } for underline in prev_scene.underlines.drain(..) { - if views.contains(&EntityId::from(underline.view_id as u64)) { + if views.contains(&underline.view_id.into()) { let order = &prev_scene.orders_by_layer[&underline.layer_id]; self.insert(&order, underline); } } for sprite in prev_scene.monochrome_sprites.drain(..) { - if views.contains(&EntityId::from(sprite.view_id as u64)) { + if views.contains(&sprite.view_id.into()) { let order = &prev_scene.orders_by_layer[&sprite.layer_id]; self.insert(&order, sprite); } } for sprite in prev_scene.polychrome_sprites.drain(..) { - if views.contains(&EntityId::from(sprite.view_id as u64)) { + if views.contains(&sprite.view_id.into()) { let order = &prev_scene.orders_by_layer[&sprite.layer_id]; self.insert(&order, sprite); } } for surface in prev_scene.surfaces.drain(..) { - if views.contains(&EntityId::from(surface.view_id as u64)) { + if views.contains(&surface.view_id.into()) { let order = &prev_scene.orders_by_layer[&surface.layer_id]; self.insert(&order, surface); } @@ -470,7 +494,7 @@ pub(crate) enum PrimitiveBatch<'a> { #[derive(Default, Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Quad { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -502,7 +526,7 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Underline { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -533,7 +557,7 @@ impl From for Primitive { #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Shadow { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -564,7 +588,7 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct MonochromeSprite { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -597,7 +621,7 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct PolychromeSprite { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -630,7 +654,7 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] pub struct Surface { - pub view_id: u32, + pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, pub bounds: Bounds, @@ -662,7 +686,7 @@ pub(crate) struct PathId(pub(crate) usize); #[derive(Debug)] pub struct Path { pub(crate) id: PathId, - pub(crate) view_id: u32, + pub(crate) view_id: ViewId, layer_id: LayerId, order: DrawOrder, pub(crate) bounds: Bounds

, @@ -678,9 +702,9 @@ impl Path { pub fn new(start: Point) -> Self { Self { id: PathId(0), - view_id: 0, - layer_id: 0, - order: 0, + view_id: ViewId::default(), + layer_id: LayerId::default(), + order: DrawOrder::default(), vertices: Vec::new(), start, current: start, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 2e84260b9e45aa89593e568296c623750189a3a8..34df4b24236be52108e68771ded67140e4a43b0c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1102,7 +1102,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, Shadow { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds: shadow_bounds.scale(scale_factor), @@ -1127,7 +1127,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, Quad { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds: quad.bounds.scale(scale_factor), @@ -1148,7 +1148,7 @@ impl<'a> WindowContext<'a> { path.content_mask = content_mask; path.color = color.into(); - path.view_id = view_id.as_u64() as u32; + path.view_id = view_id.into(); let window = &mut *self.window; window .next_frame @@ -1180,7 +1180,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, Underline { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds: bounds.scale(scale_factor), @@ -1236,7 +1236,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, MonochromeSprite { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, @@ -1290,7 +1290,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, PolychromeSprite { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, @@ -1335,7 +1335,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, MonochromeSprite { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, @@ -1374,7 +1374,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, PolychromeSprite { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, @@ -1397,7 +1397,7 @@ impl<'a> WindowContext<'a> { window.next_frame.scene.insert( &window.next_frame.z_index_stack, Surface { - view_id: view_id.as_u64() as u32, + view_id: view_id.into(), layer_id: 0, order: 0, bounds, From 1200f595a39e77c12c38f1739cf561e810108a28 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:36:45 +0100 Subject: [PATCH 111/334] Try to run clippy just for a single target --- .github/actions/check_style/action.yml | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index cb567867e4cb53898e05c5d42dc0317cfa35e55c..8312eeb500f54adcbe71c3ebd1c9a240eddea6f0 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -2,23 +2,23 @@ name: "Check formatting" description: "Checks code formatting use cargo fmt" runs: - using: "composite" - steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - rustup component add clippy + using: "composite" + steps: + - name: Install Rust + shell: bash -euxo pipefail {0} + run: | + rustup set profile minimal + rustup update stable + rustup component add clippy - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check - - name: cargo clippy - shell: bash -euxo pipefail {0} - # clippy.toml is not currently supporting specifying allowed lints - # so specify those here, and disable the rest until Zed's workspace - # will have more fixes & suppression for the standard lint set - run: | - CARGO_LOG=debug cargo -vvv clippy --workspace --all-targets --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + CARGO_LOG=debug cargo -vvv clippy --workspace --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo From d088ace4047e6a97a35b7372fc18dc0ac1823668 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 12:48:05 +0100 Subject: [PATCH 112/334] Explicitly push a node in the dispatch tree when painting a new view --- crates/editor/src/element.rs | 10 +++++----- crates/gpui/src/key_dispatch.rs | 9 --------- crates/gpui/src/view.rs | 6 +++--- crates/gpui/src/window.rs | 15 +++++++++++++-- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 53578392e8cf2c95c3415f5e5e3de950c5db4abc..7efb43bd4852fc7ef1d8e5a5a43a79ad72a0303e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -26,11 +26,11 @@ use git::diff::DiffHunkStatus; use gpui::{ div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, - CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, - InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, - ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, - Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, + CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, + InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, + SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, + TextStyle, View, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index b3fdddf00f0c20fcfeda0befab58bb0b485501bc..06d502d7780c9c59dc180d3508d7e4074009dbb6 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -73,15 +73,6 @@ impl DispatchTree { focus_id: Option, view_id: Option, ) { - // Associate a view id to this only if it is the root node for the view. - let view_id = view_id.and_then(|view_id| { - if self.view_node_ids.contains_key(&view_id) { - None - } else { - Some(view_id) - } - }); - let parent = self.node_stack.last().copied(); let node_id = DispatchNodeId(self.nodes.len()); self.nodes.push(DispatchNode { diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 961c6aef64cde75b6ea1554a0dd6c16f1c8326af..968fbbd94cd142bdfc6629539da997652f71d91c 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -97,7 +97,7 @@ impl Element for View { } fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { - cx.with_view_id(self.entity_id(), |cx| element.take().unwrap().paint(cx)); + cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx)); } } @@ -230,7 +230,7 @@ impl AnyView { available_space: Size, cx: &mut WindowContext, ) { - cx.with_view_id(self.entity_id(), |cx| { + cx.paint_view(self.entity_id(), |cx| { cx.with_absolute_element_offset(origin, |cx| { let (layout_id, mut rendered_element) = (self.request_layout)(self, cx); cx.compute_layout(layout_id, available_space); @@ -278,7 +278,7 @@ impl Element for AnyView { } fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - cx.with_view_id(self.entity_id(), |cx| { + cx.paint_view(self.entity_id(), |cx| { if !self.cache { state.element.take().unwrap().paint(cx); return; diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 34df4b24236be52108e68771ded67140e4a43b0c..3bdd8607c031008e161b64d607a6b41ff870f702 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1942,13 +1942,12 @@ impl<'a> WindowContext<'a> { focus_handle: Option, f: impl FnOnce(Option, &mut Self) -> R, ) -> R { - let parent_view_id = self.parent_view_id(); let window = &mut self.window; let focus_id = focus_handle.as_ref().map(|handle| handle.id); window .next_frame .dispatch_tree - .push_node(context.clone(), focus_id, parent_view_id); + .push_node(context.clone(), focus_id, None); let result = f(focus_handle, self); @@ -1968,6 +1967,18 @@ impl<'a> WindowContext<'a> { result } + pub(crate) fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { + self.with_view_id(view_id, |cx| { + cx.window + .next_frame + .dispatch_tree + .push_node(None, None, Some(view_id)); + let result = f(cx); + cx.window.next_frame.dispatch_tree.pop_node(); + result + }) + } + /// Update or initialize state for an element with the given id that lives across multiple /// frames. If an element with this id existed in the rendered frame, its state will be passed /// to the given closure. The state returned by the closure will be stored so it can be referenced From a5dd2535f140038645fcda055e7f8479d8334f2c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 13:56:28 +0200 Subject: [PATCH 113/334] Properly require clippy installation, try to shuffle clippy arguments co-authored-by: Piotr --- .github/actions/check_style/action.yml | 35 +++++++++++++------------- crates/editor/src/editor_tests.rs | 1 + rust-toolchain.toml | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 8312eeb500f54adcbe71c3ebd1c9a240eddea6f0..367665dd94934a57274e753228a5462c1658da5b 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -2,23 +2,22 @@ name: "Check formatting" description: "Checks code formatting use cargo fmt" runs: - using: "composite" - steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - rustup component add clippy + using: "composite" + steps: + - name: Install Rust + shell: bash -euxo pipefail {0} + run: | + rustup set profile minimal + rustup update stable - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check - - name: cargo clippy - shell: bash -euxo pipefail {0} - # clippy.toml is not currently supporting specifying allowed lints - # so specify those here, and disable the rest until Zed's workspace - # will have more fixes & suppression for the standard lint set - run: | - CARGO_LOG=debug cargo -vvv clippy --workspace --all-features -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 520c3714d3d529dbcd2df4d4cc4d750db2a7a53c..00e33a2729d7d4b42f4cb270065291a1dca374e3 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8242,6 +8242,7 @@ pub(crate) fn update_test_project_settings( } pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { + dbg!("(???????????"); _ = cx.update(|cx| { let store = SettingsStore::test(cx); cx.set_global(store); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 79d2d20a7afd81fc00eb1e142d78473562b7f335..e04ebff9ced48305cf6af00e128e4bb386f29d23 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] channel = "1.75" -components = [ "rustfmt" ] +components = [ "rustfmt", "clippy" ] targets = [ "x86_64-apple-darwin", "aarch64-apple-darwin", "wasm32-wasi" ] From 41bc49af364adf1d2193dd800546aac280fe0ce1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 14:01:42 +0200 Subject: [PATCH 114/334] Remove redundant install Rust steps Those were not installing Rust but configuring it via rustup, and those configurations were done on `stable` toolchain which is not what we use (see rust-toolchain.toml) co-authored-by: Piotr --- .github/actions/check_style/action.yml | 6 --- .github/actions/run_tests/action.yml | 41 +++++++++--------- .github/workflows/ci.yml | 8 ---- .github/workflows/randomized_tests.yml | 59 ++++++++++++-------------- .github/workflows/release_nightly.yml | 8 ---- crates/editor/src/editor_tests.rs | 1 - rust-toolchain.toml | 1 + 7 files changed, 47 insertions(+), 77 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 367665dd94934a57274e753228a5462c1658da5b..5dc7c42b02f387fe76295e9ae110f28972ef8450 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -4,12 +4,6 @@ description: "Checks code formatting use cargo fmt" runs: using: "composite" steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - - name: cargo fmt shell: bash -euxo pipefail {0} run: cargo fmt --all -- --check diff --git a/.github/actions/run_tests/action.yml b/.github/actions/run_tests/action.yml index 1ea51a06a6b4f26935e2c752beb0cad12139fcfb..af37af7fc429cc9311d033de6b22016ff1d3e24f 100644 --- a/.github/actions/run_tests/action.yml +++ b/.github/actions/run_tests/action.yml @@ -2,29 +2,26 @@ name: "Run tests" description: "Runs the tests" runs: - using: "composite" - steps: - - name: Install Rust - shell: bash -euxo pipefail {0} - run: | - rustup set profile minimal - rustup update stable - rustup target add wasm32-wasi - cargo install cargo-nextest + using: "composite" + steps: + - name: Install Rust + shell: bash -euxo pipefail {0} + run: | + cargo install cargo-nextest - - name: Install Node - uses: actions/setup-node@v3 - with: - node-version: "18" + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: "18" - - name: Limit target directory size - shell: bash -euxo pipefail {0} - run: script/clear-target-dir-if-larger-than 100 + - name: Limit target directory size + shell: bash -euxo pipefail {0} + run: script/clear-target-dir-if-larger-than 100 - - name: Run check - shell: bash -euxo pipefail {0} - run: cargo check --tests --workspace + - name: Run check + shell: bash -euxo pipefail {0} + run: cargo check --tests --workspace - - name: Run tests - shell: bash -euxo pipefail {0} - run: cargo nextest run --workspace --no-fail-fast + - name: Run tests + shell: bash -euxo pipefail {0} + run: cargo nextest run --workspace --no-fail-fast diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a92a744bb12d59fcf0539a4f8fb10f270899dd4..476f263997d5caa7fb0f3cb576397b618347e82f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,14 +76,6 @@ jobs: APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} steps: - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable - rustup target add aarch64-apple-darwin - rustup target add x86_64-apple-darwin - rustup target add wasm32-wasi - - name: Install Node uses: actions/setup-node@v3 with: diff --git a/.github/workflows/randomized_tests.yml b/.github/workflows/randomized_tests.yml index d1b8ddfdfbbe636c1f9817e134e0fc86871d5a03..a1704d58bdcffec9ce51779bf9af47b8be7fec13 100644 --- a/.github/workflows/randomized_tests.yml +++ b/.github/workflows/randomized_tests.yml @@ -3,41 +3,36 @@ name: Randomized Tests concurrency: randomized-tests on: - push: - branches: - - randomized-tests-runner - # schedule: - # - cron: '0 * * * *' + push: + branches: + - randomized-tests-runner + # schedule: + # - cron: '0 * * * *' env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - RUST_BACKTRACE: 1 - ZED_SERVER_URL: https://zed.dev - ZED_CLIENT_SECRET_TOKEN: ${{ secrets.ZED_CLIENT_SECRET_TOKEN }} + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUST_BACKTRACE: 1 + ZED_SERVER_URL: https://zed.dev + ZED_CLIENT_SECRET_TOKEN: ${{ secrets.ZED_CLIENT_SECRET_TOKEN }} jobs: - tests: - name: Run randomized tests - runs-on: - - self-hosted - - randomized-tests - steps: - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable + tests: + name: Run randomized tests + runs-on: + - self-hosted + - randomized-tests + steps: + - name: Install Node + uses: actions/setup-node@v3 + with: + node-version: "18" - - name: Install Node - uses: actions/setup-node@v3 - with: - node-version: '18' + - name: Checkout repo + uses: actions/checkout@v3 + with: + clean: false + submodules: "recursive" - - name: Checkout repo - uses: actions/checkout@v3 - with: - clean: false - submodules: 'recursive' - - - name: Run randomized tests - run: script/randomized-test-ci + - name: Run randomized tests + run: script/randomized-test-ci diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 5d2dbe41f9aeceb18e0c35a064325dfd9bd4b31a..33ccb4cba93ce6dd6538a81426248c866c4ee81b 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -60,14 +60,6 @@ jobs: DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} steps: - - name: Install Rust - run: | - rustup set profile minimal - rustup update stable - rustup target add aarch64-apple-darwin - rustup target add x86_64-apple-darwin - rustup target add wasm32-wasi - - name: Install Node uses: actions/setup-node@v3 with: diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 00e33a2729d7d4b42f4cb270065291a1dca374e3..520c3714d3d529dbcd2df4d4cc4d750db2a7a53c 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8242,7 +8242,6 @@ pub(crate) fn update_test_project_settings( } pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) { - dbg!("(???????????"); _ = cx.update(|cx| { let store = SettingsStore::test(cx); cx.set_global(store); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e04ebff9ced48305cf6af00e128e4bb386f29d23..5cdc76def26ae769f69b0f69555ec5663fcf1937 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,5 @@ [toolchain] channel = "1.75" +profile = "minimal" components = [ "rustfmt", "clippy" ] targets = [ "x86_64-apple-darwin", "aarch64-apple-darwin", "wasm32-wasi" ] From 50ccdf5c167897baf39a929f8ec714d053733631 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 13:22:59 +0100 Subject: [PATCH 115/334] Reuse input handler when reusing a view tree --- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/mac/window.rs | 4 +- crates/gpui/src/platform/test/window.rs | 4 +- crates/gpui/src/window.rs | 86 ++++++++++++++++++------- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index c41bf998f698443d5b6ca93dee2966206596d533..5a2335919ebe6ff9b2277e02d4f6f55b4dc9a80c 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -145,7 +145,7 @@ pub trait PlatformWindow { fn modifiers(&self) -> Modifiers; fn as_any_mut(&mut self) -> &mut dyn Any; fn set_input_handler(&mut self, input_handler: Box); - fn clear_input_handler(&mut self); + fn take_input_handler(&mut self) -> Option>; fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); fn set_title(&mut self, title: &str); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 7c96fc0198895ce426f483c2c03ce0c888df8d3c..534c43284078697333c6a7e95186160b13302ad6 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -770,8 +770,8 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().input_handler = Some(input_handler); } - fn clear_input_handler(&mut self) { - self.0.as_ref().lock().input_handler = None; + fn take_input_handler(&mut self) -> Option> { + self.0.as_ref().lock().input_handler.take() } fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver { diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 029fd7300998f55f089696d6e01199b9c687ddfd..f05e13e3a027e2be9d4f17690fe7162a73396d03 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -167,8 +167,8 @@ impl PlatformWindow for TestWindow { self.0.lock().input_handler = Some(input_handler); } - fn clear_input_handler(&mut self) { - self.0.lock().input_handler = None; + fn take_input_handler(&mut self) -> Option> { + self.0.lock().input_handler.take() } fn prompt( diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 3bdd8607c031008e161b64d607a6b41ff870f702..65f6aa42fffdaab131f4f9c96e65abb756ad5071 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -296,6 +296,11 @@ pub(crate) struct ElementStateBox { type_name: &'static str, } +struct RequestedInputHandler { + view_id: EntityId, + handler: Option>, +} + pub(crate) struct Frame { focus: Option, window_active: bool, @@ -308,6 +313,7 @@ pub(crate) struct Frame { pub(crate) next_stacking_order_id: u32, content_mask_stack: Vec>, element_offset_stack: Vec>, + requested_input_handler: Option, pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, } @@ -323,9 +329,10 @@ impl Frame { scene: Scene::default(), z_index_stack: StackingOrder::default(), next_stacking_order_id: 0, - depth_map: Default::default(), + depth_map: Vec::new(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), + requested_input_handler: None, view_stack: Vec::new(), reused_views: FxHashSet::default(), } @@ -947,7 +954,7 @@ impl<'a> WindowContext<'a> { &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let order = self.window.next_frame.z_index_stack.clone(); self.window .next_frame @@ -1036,7 +1043,7 @@ impl<'a> WindowContext<'a> { /// Called during painting to track which z-index is on top at each pixel position pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); self.window .next_frame .depth_map @@ -1093,7 +1100,7 @@ impl<'a> WindowContext<'a> { ) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; for shadow in shadows { let mut shadow_bounds = bounds; @@ -1121,7 +1128,7 @@ impl<'a> WindowContext<'a> { pub fn paint_quad(&mut self, quad: PaintQuad) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1144,7 +1151,7 @@ impl<'a> WindowContext<'a> { pub fn paint_path(&mut self, mut path: Path, color: impl Into) { let scale_factor = self.scale_factor(); let content_mask = self.content_mask(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); path.content_mask = content_mask; path.color = color.into(); @@ -1174,7 +1181,7 @@ impl<'a> WindowContext<'a> { size: size(width, height), }; let content_mask = self.content_mask(); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1231,7 +1238,7 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( &window.next_frame.z_index_stack, @@ -1284,7 +1291,7 @@ impl<'a> WindowContext<'a> { size: tile.bounds.size.map(Into::into), }; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1329,7 +1336,7 @@ impl<'a> WindowContext<'a> { Ok((params.size, Cow::Owned(bytes))) })?; let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1368,7 +1375,7 @@ impl<'a> WindowContext<'a> { })?; let content_mask = self.content_mask().scale(scale_factor); let corner_radii = corner_radii.scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( @@ -1392,7 +1399,7 @@ impl<'a> WindowContext<'a> { let scale_factor = self.scale_factor(); let bounds = bounds.scale(scale_factor); let content_mask = self.content_mask().scale(scale_factor); - let view_id = self.parent_view_id().unwrap(); + let view_id = self.parent_view_id(); let window = &mut *self.window; window.next_frame.scene.insert( &window.next_frame.z_index_stack, @@ -1408,14 +1415,27 @@ impl<'a> WindowContext<'a> { } pub(crate) fn reuse_view(&mut self) { - let view_id = self.parent_view_id().unwrap(); - let window = &mut self.window; - let grafted_view_ids = window + let view_id = self.parent_view_id(); + let grafted_view_ids = self + .window .next_frame .dispatch_tree - .graft(view_id, &mut window.rendered_frame.dispatch_tree); + .graft(view_id, &mut self.window.rendered_frame.dispatch_tree); for view_id in grafted_view_ids { - assert!(window.next_frame.reused_views.insert(view_id)); + assert!(self.window.next_frame.reused_views.insert(view_id)); + + // Reuse the previous input handler if it was associated with one of + // the views grafted from the tree in the previous frame. + if self + .window + .rendered_frame + .requested_input_handler + .as_ref() + .map_or(false, |requested| requested.view_id == view_id) + { + self.window.next_frame.requested_input_handler = + self.window.rendered_frame.requested_input_handler.take(); + } } } @@ -1430,7 +1450,11 @@ impl<'a> WindowContext<'a> { } self.text_system().start_frame(); - self.window.platform_window.clear_input_handler(); + if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut() + { + requested_handler.handler = self.window.platform_window.take_input_handler(); + } + self.window.layout_engine.as_mut().unwrap().clear(); self.window.next_frame.clear(); let root_view = self.window.root_view.take().unwrap(); @@ -1486,6 +1510,13 @@ impl<'a> WindowContext<'a> { .reuse_views(&mut self.window.rendered_frame); self.window.next_frame.scene.finish(); + // Register requested input handler with the platform window. + if let Some(requested_input) = self.window.next_frame.requested_input_handler.as_mut() { + if let Some(handler) = requested_input.handler.take() { + self.window.platform_window.set_input_handler(handler); + } + } + let previous_focus_path = self.window.rendered_frame.focus_path(); let previous_window_active = self.window.rendered_frame.window_active; mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); @@ -2054,7 +2085,7 @@ impl<'a> WindowContext<'a> { result } else { let (result, state) = f(None, cx); - let parent_view_id = cx.parent_view_id().unwrap(); + let parent_view_id = cx.parent_view_id(); cx.window_mut() .next_frame .element_states @@ -2072,8 +2103,13 @@ impl<'a> WindowContext<'a> { }) } - fn parent_view_id(&self) -> Option { - self.window.next_frame.view_stack.last().copied() + fn parent_view_id(&self) -> EntityId { + *self + .window + .next_frame + .view_stack + .last() + .expect("a view should always be on the stack while drawing") } /// Set an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the @@ -2087,9 +2123,11 @@ impl<'a> WindowContext<'a> { input_handler: impl PlatformInputHandler, ) { if focus_handle.is_focused(self) { - self.window - .platform_window - .set_input_handler(Box::new(input_handler)); + let view_id = self.parent_view_id(); + self.window.next_frame.requested_input_handler = Some(RequestedInputHandler { + view_id, + handler: Some(Box::new(input_handler)), + }) } } From a33be893a4d90384dc721a900185e1635312d908 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 12:24:16 +0100 Subject: [PATCH 116/334] chore: Revert asset compression While it does reduce the size of a binary quite significantly, it doesn't seem to matter for .dmg which runs it's own compression on top of binaries. --- Cargo.lock | 67 ------------------------------------------------------ Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a49aa72e2b3cb69c5fefe01836f86b7cd9e03a5..48c17f867223afd4c70a75dc24eea85a68799291 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3440,40 +3440,6 @@ dependencies = [ "tiff", ] -[[package]] -name = "include-flate" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e11569346406931d20276cc460215ee2826e7cad43aa986999cb244dd7adb0" -dependencies = [ - "include-flate-codegen-exports", - "lazy_static", - "libflate", -] - -[[package]] -name = "include-flate-codegen" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a7d6e1419fa3129eb0802b4c99603c0d425c79fb5d76191d5a20d0ab0d664e8" -dependencies = [ - "libflate", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "include-flate-codegen-exports" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75657043ffe3d8280f1cb8aef0f505532b392ed7758e0baeac22edadcee31a03" -dependencies = [ - "include-flate-codegen", - "proc-macro-hack", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -3865,26 +3831,6 @@ version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" -[[package]] -name = "libflate" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" -dependencies = [ - "adler32", - "crc32fast", - "libflate_lz77", -] - -[[package]] -name = "libflate_lz77" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" -dependencies = [ - "rle-decode-fast", -] - [[package]] name = "libgit2-sys" version = "0.14.2+1.5.1" @@ -5462,12 +5408,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.67" @@ -6162,12 +6102,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rle-decode-fast" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" - [[package]] name = "rmp" version = "0.8.12" @@ -6315,7 +6249,6 @@ version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" dependencies = [ - "include-flate", "rust-embed-impl", "rust-embed-utils", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 7ea79f094c0b6d5d5cd4a1e63f70059db05ec6e3..79d28821d4b040f35fc1ab1dd391cddeee93bc47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ prost = { version = "0.8" } rand = { version = "0.8.5" } refineable = { path = "./crates/refineable" } regex = { version = "1.5" } -rust-embed = { version = "8.0", features = ["include-exclude", "compression"] } +rust-embed = { version = "8.0", features = ["include-exclude"] } rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } schemars = { version = "0.8" } serde = { version = "1.0", features = ["derive", "rc"] } From 2e36b0b72accb8c7bf7d67c376eca063c02e0dbe Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 15:09:43 +0200 Subject: [PATCH 117/334] Do not split on only external directories being drag and dropped --- crates/workspace/src/pane.rs | 52 ++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index c4602bb1adf91d89d70272c8d89082ffdda93b14..aec33c2dd32ee9bda77e7fe7927e1346a86178df 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -6,6 +6,7 @@ use crate::{ }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; +use futures::{stream::FuturesUnordered, StreamExt}; use gpui::{ actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyElement, AppContext, AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, ExternalPaths, @@ -1796,23 +1797,46 @@ impl Pane { } } let mut to_pane = cx.view().clone(); - let split_direction = self.drag_split_direction; + let mut split_direction = self.drag_split_direction; let paths = paths.paths().to_vec(); self.workspace - .update(cx, |_, cx| { - cx.defer(move |workspace, cx| { - if let Some(split_direction) = split_direction { - to_pane = workspace.split_pane(to_pane, split_direction, cx); + .update(cx, |workspace, cx| { + let fs = Arc::clone(workspace.project().read(cx).fs()); + cx.spawn(|workspace, mut cx| async move { + let mut is_file_checks = FuturesUnordered::new(); + for path in &paths { + is_file_checks.push(fs.is_file(path)) } - workspace - .open_paths( - paths, - OpenVisible::OnlyDirectories, - Some(to_pane.downgrade()), - cx, - ) - .detach(); - }); + let mut has_files_to_open = false; + while let Some(is_file) = is_file_checks.next().await { + if is_file { + has_files_to_open = true; + break; + } + } + drop(is_file_checks); + if !has_files_to_open { + split_direction = None; + } + + if let Some(open_task) = workspace + .update(&mut cx, |workspace, cx| { + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); + } + workspace.open_paths( + paths, + OpenVisible::OnlyDirectories, + Some(to_pane.downgrade()), + cx, + ) + }) + .ok() + { + let _opened_items: Vec<_> = open_task.await; + } + }) + .detach(); }) .log_err(); } From 18eaefd0ed31b90d5342c58e853365be61dfe0d0 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 15:03:34 +0100 Subject: [PATCH 118/334] Reuse cursor style when reusing a view tree --- crates/gpui/src/window.rs | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 65f6aa42fffdaab131f4f9c96e65abb756ad5071..7e38353e477d4002d4ee3dde58f1f86be3dba21e 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -273,7 +273,6 @@ pub struct Window { default_prevented: bool, mouse_position: Point, modifiers: Modifiers, - requested_cursor_style: Option, scale_factor: f32, bounds: WindowBounds, bounds_observers: SubscriberSet<(), AnyObserver>, @@ -314,6 +313,8 @@ pub(crate) struct Frame { content_mask_stack: Vec>, element_offset_stack: Vec>, requested_input_handler: Option, + cursor_styles: FxHashMap, + requested_cursor_style: Option, pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, } @@ -333,6 +334,8 @@ impl Frame { content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), requested_input_handler: None, + cursor_styles: FxHashMap::default(), + requested_cursor_style: None, view_stack: Vec::new(), reused_views: FxHashSet::default(), } @@ -346,6 +349,9 @@ impl Frame { self.next_stacking_order_id = 0; self.reused_views.clear(); self.scene.clear(); + self.requested_input_handler.take(); + self.cursor_styles.clear(); + self.requested_cursor_style.take(); debug_assert_eq!(self.view_stack.len(), 0); } @@ -469,7 +475,6 @@ impl Window { default_prevented: true, mouse_position, modifiers, - requested_cursor_style: None, scale_factor, bounds, bounds_observers: SubscriberSet::new(), @@ -1037,7 +1042,9 @@ impl<'a> WindowContext<'a> { /// Update the cursor style at the platform level. pub fn set_cursor_style(&mut self, style: CursorStyle) { - self.window.requested_cursor_style = Some(style) + let view_id = self.parent_view_id(); + self.window.next_frame.cursor_styles.insert(view_id, style); + self.window.next_frame.requested_cursor_style = Some(style); } /// Called during painting to track which z-index is on top at each pixel position @@ -1436,6 +1443,11 @@ impl<'a> WindowContext<'a> { self.window.next_frame.requested_input_handler = self.window.rendered_frame.requested_input_handler.take(); } + + if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) { + self.window.next_frame.cursor_styles.insert(view_id, style); + self.window.next_frame.requested_cursor_style = Some(style); + } } } @@ -1505,6 +1517,17 @@ impl<'a> WindowContext<'a> { self.window.next_frame.window_active = self.window.active; self.window.root_view = Some(root_view); + // Set the cursor only if we're the active window. + let cursor_style = self + .window + .next_frame + .requested_cursor_style + .take() + .unwrap_or(CursorStyle::Arrow); + if self.is_window_active() { + self.platform.set_cursor_style(cursor_style); + } + self.window .next_frame .reuse_views(&mut self.window.rendered_frame); @@ -1523,16 +1546,6 @@ impl<'a> WindowContext<'a> { let current_focus_path = self.window.rendered_frame.focus_path(); let current_window_active = self.window.rendered_frame.window_active; - // Set the cursor only if we're the active window. - let cursor_style = self - .window - .requested_cursor_style - .take() - .unwrap_or(CursorStyle::Arrow); - if self.is_window_active() { - self.platform.set_cursor_style(cursor_style); - } - self.window.refreshing = false; self.window.drawing = false; ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); From 9bb50a5ded75e7f4eece784178ac4b4d23c90411 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 16:16:48 +0200 Subject: [PATCH 119/334] Restore hover action in the editor --- crates/editor/src/hover_popover.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 26ce3e5cf708e0193eef2c87fbf72c27f4da806a..8da2f50c198a01136d1318922a5159e3814a894f 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -2,13 +2,13 @@ use crate::{ display_map::{InlayOffset, ToDisplayPoint}, link_go_to_definition::{InlayHighlight, RangeInEditor}, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, - ExcerptId, RangeToAnchorExt, + ExcerptId, Hover, RangeToAnchorExt, }; use futures::FutureExt; use gpui::{ - actions, div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model, - MouseButton, ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, - Task, ViewContext, WeakView, + div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model, MouseButton, + ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, Task, + ViewContext, WeakView, }; use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; @@ -27,8 +27,6 @@ pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.); pub const HOVER_POPOVER_GAP: Pixels = px(10.); -actions!(editor, [Hover]); - /// Bindable action which uses the most recent selection head to trigger a hover pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { let head = editor.selections.newest_display(cx).head(); From ba83623c84d08913468445a1a43266abf7dcb878 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 17:43:49 +0200 Subject: [PATCH 120/334] Fix whitespace symbol colors in the editor, use zed1 one co-authored-by: Marshall Bowers --- crates/editor/src/editor.rs | 2 -- crates/theme/src/themes/andromeda.rs | 2 +- crates/theme/src/themes/atelier.rs | 40 ++++++++++----------- crates/theme/src/themes/ayu.rs | 6 ++-- crates/theme/src/themes/gruvbox.rs | 12 +++---- crates/theme/src/themes/one.rs | 4 +-- crates/theme/src/themes/rose_pine.rs | 6 ++-- crates/theme/src/themes/sandcastle.rs | 2 +- crates/theme/src/themes/solarized.rs | 4 +-- crates/theme/src/themes/summercamp.rs | 2 +- crates/theme_importer/src/zed1/converter.rs | 2 +- 11 files changed, 40 insertions(+), 42 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cd0586588e9212a163135c6a5e59e12390796588..7fe942f14561c8781b17c9ae66ea356190425422 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7677,7 +7677,6 @@ impl Editor { scrollbar_width: cx.editor_style.scrollbar_width, syntax: cx.editor_style.syntax.clone(), status: cx.editor_style.status.clone(), - // todo!("what about the rest of the highlight style parts for inlays and suggestions?") inlays_style: HighlightStyle { color: Some(cx.theme().status().hint), font_weight: Some(FontWeight::BOLD), @@ -9350,7 +9349,6 @@ impl Render for Editor { scrollbar_width: px(12.), syntax: cx.theme().syntax().clone(), status: cx.theme().status().clone(), - // todo!("what about the rest of the highlight style parts?") inlays_style: HighlightStyle { color: Some(cx.theme().status().hint), font_weight: Some(FontWeight::BOLD), diff --git a/crates/theme/src/themes/andromeda.rs b/crates/theme/src/themes/andromeda.rs index effdfb85f97a0c9baa1d4b9881b6c89de9ec1245..45dc66094518c5ab25fe2ad12e616213e4520994 100644 --- a/crates/theme/src/themes/andromeda.rs +++ b/crates/theme/src/themes/andromeda.rs @@ -69,7 +69,7 @@ pub fn andromeda() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x21242bff).into()), editor_line_number: Some(rgba(0xf7f7f859).into()), editor_active_line_number: Some(rgba(0xf7f7f8ff).into()), - editor_invisible: Some(rgba(0xaca8aeff).into()), + editor_invisible: Some(rgba(0x64646dff).into()), editor_wrap_guide: Some(rgba(0xf7f7f80d).into()), editor_active_wrap_guide: Some(rgba(0xf7f7f81a).into()), editor_document_highlight_read_background: Some(rgba(0x11a7931a).into()), diff --git a/crates/theme/src/themes/atelier.rs b/crates/theme/src/themes/atelier.rs index 6848676e00734821a4bf44f5b6c6a67cd5bf54ac..e0682b217e0442a53d7c25e6ac8e1339fc55524b 100644 --- a/crates/theme/src/themes/atelier.rs +++ b/crates/theme/src/themes/atelier.rs @@ -70,7 +70,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x221f26ff).into()), editor_line_number: Some(rgba(0xefecf459).into()), editor_active_line_number: Some(rgba(0xefecf4ff).into()), - editor_invisible: Some(rgba(0x898591ff).into()), + editor_invisible: Some(rgba(0x726c7aff).into()), editor_wrap_guide: Some(rgba(0xefecf40d).into()), editor_active_wrap_guide: Some(rgba(0xefecf41a).into()), editor_document_highlight_read_background: Some(rgba(0x576dda1a).into()), @@ -535,7 +535,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe6e3ebff).into()), editor_line_number: Some(rgba(0x19171c59).into()), editor_active_line_number: Some(rgba(0x19171cff).into()), - editor_invisible: Some(rgba(0x5a5462ff).into()), + editor_invisible: Some(rgba(0x726c7aff).into()), editor_wrap_guide: Some(rgba(0x19171c0d).into()), editor_active_wrap_guide: Some(rgba(0x19171c1a).into()), editor_document_highlight_read_background: Some(rgba(0x586dda1a).into()), @@ -1000,7 +1000,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x262622ff).into()), editor_line_number: Some(rgba(0xfefbec59).into()), editor_active_line_number: Some(rgba(0xfefbecff).into()), - editor_invisible: Some(rgba(0xa4a08bff).into()), + editor_invisible: Some(rgba(0x8b8874ff).into()), editor_wrap_guide: Some(rgba(0xfefbec0d).into()), editor_active_wrap_guide: Some(rgba(0xfefbec1a).into()), editor_document_highlight_read_background: Some(rgba(0x6684e01a).into()), @@ -1465,7 +1465,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xeeebd7ff).into()), editor_line_number: Some(rgba(0x20201d59).into()), editor_active_line_number: Some(rgba(0x20201dff).into()), - editor_invisible: Some(rgba(0x706d5fff).into()), + editor_invisible: Some(rgba(0x8b8874ff).into()), editor_wrap_guide: Some(rgba(0x20201d0d).into()), editor_active_wrap_guide: Some(rgba(0x20201d1a).into()), editor_document_highlight_read_background: Some(rgba(0x6784e01a).into()), @@ -1930,7 +1930,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x2c2b23ff).into()), editor_line_number: Some(rgba(0xf4f3ec59).into()), editor_active_line_number: Some(rgba(0xf4f3ecff).into()), - editor_invisible: Some(rgba(0x91907fff).into()), + editor_invisible: Some(rgba(0x7a7867ff).into()), editor_wrap_guide: Some(rgba(0xf4f3ec0d).into()), editor_active_wrap_guide: Some(rgba(0xf4f3ec1a).into()), editor_document_highlight_read_background: Some(rgba(0x37a1661a).into()), @@ -2395,7 +2395,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xebeae3ff).into()), editor_line_number: Some(rgba(0x22221b59).into()), editor_active_line_number: Some(rgba(0x22221bff).into()), - editor_invisible: Some(rgba(0x61604fff).into()), + editor_invisible: Some(rgba(0x7a7867ff).into()), editor_wrap_guide: Some(rgba(0x22221b0d).into()), editor_active_wrap_guide: Some(rgba(0x22221b1a).into()), editor_document_highlight_read_background: Some(rgba(0x38a1661a).into()), @@ -2860,7 +2860,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x27211eff).into()), editor_line_number: Some(rgba(0xf1efee59).into()), editor_active_line_number: Some(rgba(0xf1efeeff).into()), - editor_invisible: Some(rgba(0xa79f9dff).into()), + editor_invisible: Some(rgba(0x89817eff).into()), editor_wrap_guide: Some(rgba(0xf1efee0d).into()), editor_active_wrap_guide: Some(rgba(0xf1efee1a).into()), editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()), @@ -3325,7 +3325,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe9e6e4ff).into()), editor_line_number: Some(rgba(0x1b191859).into()), editor_active_line_number: Some(rgba(0x1b1918ff).into()), - editor_invisible: Some(rgba(0x6a6360ff).into()), + editor_invisible: Some(rgba(0x89817eff).into()), editor_wrap_guide: Some(rgba(0x1b19180d).into()), editor_active_wrap_guide: Some(rgba(0x1b19181a).into()), editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()), @@ -3790,7 +3790,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x252025ff).into()), editor_line_number: Some(rgba(0xf7f3f759).into()), editor_active_line_number: Some(rgba(0xf7f3f7ff).into()), - editor_invisible: Some(rgba(0xa99aa9ff).into()), + editor_invisible: Some(rgba(0x8b7c8bff).into()), editor_wrap_guide: Some(rgba(0xf7f3f70d).into()), editor_active_wrap_guide: Some(rgba(0xf7f3f71a).into()), editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()), @@ -4255,7 +4255,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe1d6e1ff).into()), editor_line_number: Some(rgba(0x1b181b59).into()), editor_active_line_number: Some(rgba(0x1b181bff).into()), - editor_invisible: Some(rgba(0x6b5e6bff).into()), + editor_invisible: Some(rgba(0x8b7c8bff).into()), editor_wrap_guide: Some(rgba(0x1b181b0d).into()), editor_active_wrap_guide: Some(rgba(0x1b181b1a).into()), editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()), @@ -4720,7 +4720,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1c2529ff).into()), editor_line_number: Some(rgba(0xebf8ff59).into()), editor_active_line_number: Some(rgba(0xebf8ffff).into()), - editor_invisible: Some(rgba(0x7ca0b3ff).into()), + editor_invisible: Some(rgba(0x66889aff).into()), editor_wrap_guide: Some(rgba(0xebf8ff0d).into()), editor_active_wrap_guide: Some(rgba(0xebf8ff1a).into()), editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()), @@ -5185,7 +5185,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xcdeaf9ff).into()), editor_line_number: Some(rgba(0x161b1d59).into()), editor_active_line_number: Some(rgba(0x161b1dff).into()), - editor_invisible: Some(rgba(0x526f7dff).into()), + editor_invisible: Some(rgba(0x66889aff).into()), editor_wrap_guide: Some(rgba(0x161b1d0d).into()), editor_active_wrap_guide: Some(rgba(0x161b1d1a).into()), editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()), @@ -5650,7 +5650,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x252020ff).into()), editor_line_number: Some(rgba(0xf4ecec59).into()), editor_active_line_number: Some(rgba(0xf4ececff).into()), - editor_invisible: Some(rgba(0x898383ff).into()), + editor_invisible: Some(rgba(0x726a6aff).into()), editor_wrap_guide: Some(rgba(0xf4ecec0d).into()), editor_active_wrap_guide: Some(rgba(0xf4ecec1a).into()), editor_document_highlight_read_background: Some(rgba(0x7272ca1a).into()), @@ -6115,7 +6115,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xebe3e3ff).into()), editor_line_number: Some(rgba(0x1b181859).into()), editor_active_line_number: Some(rgba(0x1b1818ff).into()), - editor_invisible: Some(rgba(0x5a5252ff).into()), + editor_invisible: Some(rgba(0x726a6aff).into()), editor_wrap_guide: Some(rgba(0x1b18180d).into()), editor_active_wrap_guide: Some(rgba(0x1b18181a).into()), editor_document_highlight_read_background: Some(rgba(0x7372ca1a).into()), @@ -6580,7 +6580,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1f2621ff).into()), editor_line_number: Some(rgba(0xecf4ee59).into()), editor_active_line_number: Some(rgba(0xecf4eeff).into()), - editor_invisible: Some(rgba(0x859188ff).into()), + editor_invisible: Some(rgba(0x6c7a71ff).into()), editor_wrap_guide: Some(rgba(0xecf4ee0d).into()), editor_active_wrap_guide: Some(rgba(0xecf4ee1a).into()), editor_document_highlight_read_background: Some(rgba(0x478c901a).into()), @@ -7045,7 +7045,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe3ebe6ff).into()), editor_line_number: Some(rgba(0x171c1959).into()), editor_active_line_number: Some(rgba(0x171c19ff).into()), - editor_invisible: Some(rgba(0x546259ff).into()), + editor_invisible: Some(rgba(0x6c7a71ff).into()), editor_wrap_guide: Some(rgba(0x171c190d).into()), editor_active_wrap_guide: Some(rgba(0x171c191a).into()), editor_document_highlight_read_background: Some(rgba(0x488c901a).into()), @@ -7510,7 +7510,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1f231fff).into()), editor_line_number: Some(rgba(0xf4fbf459).into()), editor_active_line_number: Some(rgba(0xf4fbf4ff).into()), - editor_invisible: Some(rgba(0x8ba48bff).into()), + editor_invisible: Some(rgba(0x748b74ff).into()), editor_wrap_guide: Some(rgba(0xf4fbf40d).into()), editor_active_wrap_guide: Some(rgba(0xf4fbf41a).into()), editor_document_highlight_read_background: Some(rgba(0x3e62f41a).into()), @@ -7975,7 +7975,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xdaeedaff).into()), editor_line_number: Some(rgba(0x13151359).into()), editor_active_line_number: Some(rgba(0x131513ff).into()), - editor_invisible: Some(rgba(0x5f705fff).into()), + editor_invisible: Some(rgba(0x748b74ff).into()), editor_wrap_guide: Some(rgba(0x1315130d).into()), editor_active_wrap_guide: Some(rgba(0x1315131a).into()), editor_document_highlight_read_background: Some(rgba(0x3f62f41a).into()), @@ -8440,7 +8440,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x262f51ff).into()), editor_line_number: Some(rgba(0xf5f7ff59).into()), editor_active_line_number: Some(rgba(0xf5f7ffff).into()), - editor_invisible: Some(rgba(0x959bb2ff).into()), + editor_invisible: Some(rgba(0x7a819cff).into()), editor_wrap_guide: Some(rgba(0xf5f7ff0d).into()), editor_active_wrap_guide: Some(rgba(0xf5f7ff1a).into()), editor_document_highlight_read_background: Some(rgba(0x3e8fd01a).into()), @@ -8905,7 +8905,7 @@ pub fn atelier() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xe5e8f5ff).into()), editor_line_number: Some(rgba(0x20274659).into()), editor_active_line_number: Some(rgba(0x202746ff).into()), - editor_invisible: Some(rgba(0x606889ff).into()), + editor_invisible: Some(rgba(0x7a819cff).into()), editor_wrap_guide: Some(rgba(0x2027460d).into()), editor_active_wrap_guide: Some(rgba(0x2027461a).into()), editor_document_highlight_read_background: Some(rgba(0x3f8fd01a).into()), diff --git a/crates/theme/src/themes/ayu.rs b/crates/theme/src/themes/ayu.rs index a0402825c1a1eaf5e6f91986c47505c68772c743..9c9030b2f27928c5e9fb16a6e536de84036c4f8e 100644 --- a/crates/theme/src/themes/ayu.rs +++ b/crates/theme/src/themes/ayu.rs @@ -70,7 +70,7 @@ pub fn ayu() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1f2127ff).into()), editor_line_number: Some(rgba(0xbfbdb659).into()), editor_active_line_number: Some(rgba(0xbfbdb6ff).into()), - editor_invisible: Some(rgba(0x8a8986ff).into()), + editor_invisible: Some(rgba(0x666767ff).into()), editor_wrap_guide: Some(rgba(0xbfbdb60d).into()), editor_active_wrap_guide: Some(rgba(0xbfbdb61a).into()), editor_document_highlight_read_background: Some(rgba(0x5ac2fe1a).into()), @@ -514,7 +514,7 @@ pub fn ayu() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xececedff).into()), editor_line_number: Some(rgba(0x5c616659).into()), editor_active_line_number: Some(rgba(0x5c6166ff).into()), - editor_invisible: Some(rgba(0x8c8f93ff).into()), + editor_invisible: Some(rgba(0xacafb1ff).into()), editor_wrap_guide: Some(rgba(0x5c61660d).into()), editor_active_wrap_guide: Some(rgba(0x5c61661a).into()), editor_document_highlight_read_background: Some(rgba(0x3b9ee51a).into()), @@ -958,7 +958,7 @@ pub fn ayu() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x353944ff).into()), editor_line_number: Some(rgba(0xcccac259).into()), editor_active_line_number: Some(rgba(0xcccac2ff).into()), - editor_invisible: Some(rgba(0x9a9a98ff).into()), + editor_invisible: Some(rgba(0x787a7cff).into()), editor_wrap_guide: Some(rgba(0xcccac20d).into()), editor_active_wrap_guide: Some(rgba(0xcccac21a).into()), editor_document_highlight_read_background: Some(rgba(0x73cffe1a).into()), diff --git a/crates/theme/src/themes/gruvbox.rs b/crates/theme/src/themes/gruvbox.rs index 5a319aa37a69b07998fd06e082cf0f370376abb0..34ccefee1172cb9aee9d85496f2fdce40ab4abd2 100644 --- a/crates/theme/src/themes/gruvbox.rs +++ b/crates/theme/src/themes/gruvbox.rs @@ -70,7 +70,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x3a3735ff).into()), editor_line_number: Some(rgba(0xfbf1c759).into()), editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), - editor_invisible: Some(rgba(0xc5b597ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), @@ -521,7 +521,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x393634ff).into()), editor_line_number: Some(rgba(0xfbf1c759).into()), editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), - editor_invisible: Some(rgba(0xc5b597ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), @@ -972,7 +972,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x3b3735ff).into()), editor_line_number: Some(rgba(0xfbf1c759).into()), editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), - editor_invisible: Some(rgba(0xc5b597ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), @@ -1423,7 +1423,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xecddb4ff).into()), editor_line_number: Some(rgba(0x28282859).into()), editor_active_line_number: Some(rgba(0x282828ff).into()), - editor_invisible: Some(rgba(0x5f5650ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0x2828280d).into()), editor_active_wrap_guide: Some(rgba(0x2828281a).into()), editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), @@ -1874,7 +1874,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xecddb5ff).into()), editor_line_number: Some(rgba(0x28282859).into()), editor_active_line_number: Some(rgba(0x282828ff).into()), - editor_invisible: Some(rgba(0x5f5650ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0x2828280d).into()), editor_active_wrap_guide: Some(rgba(0x2828281a).into()), editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), @@ -2325,7 +2325,7 @@ pub fn gruvbox() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xecdcb3ff).into()), editor_line_number: Some(rgba(0x28282859).into()), editor_active_line_number: Some(rgba(0x282828ff).into()), - editor_invisible: Some(rgba(0x5f5650ff).into()), + editor_invisible: Some(rgba(0x928474ff).into()), editor_wrap_guide: Some(rgba(0x2828280d).into()), editor_active_wrap_guide: Some(rgba(0x2828281a).into()), editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), diff --git a/crates/theme/src/themes/one.rs b/crates/theme/src/themes/one.rs index 3c8eb1085f0380bdc2df70acb6f5cb5472c88af0..5928939f7a5a51607f20cb7779ce35ad93a1278c 100644 --- a/crates/theme/src/themes/one.rs +++ b/crates/theme/src/themes/one.rs @@ -70,7 +70,7 @@ pub fn one() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x2f343eff).into()), editor_line_number: Some(rgba(0xc8ccd459).into()), editor_active_line_number: Some(rgba(0xc8ccd4ff).into()), - editor_invisible: Some(rgba(0x838994ff).into()), + editor_invisible: Some(rgba(0x555a63ff).into()), editor_wrap_guide: Some(rgba(0xc8ccd40d).into()), editor_active_wrap_guide: Some(rgba(0xc8ccd41a).into()), editor_document_highlight_read_background: Some(rgba(0x74ade81a).into()), @@ -521,7 +521,7 @@ pub fn one() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xebebecff).into()), editor_line_number: Some(rgba(0x383a4159).into()), editor_active_line_number: Some(rgba(0x383a41ff).into()), - editor_invisible: Some(rgba(0x7f8188ff).into()), + editor_invisible: Some(rgba(0xa3a3a4ff).into()), editor_wrap_guide: Some(rgba(0x383a410d).into()), editor_active_wrap_guide: Some(rgba(0x383a411a).into()), editor_document_highlight_read_background: Some(rgba(0x5c79e21a).into()), diff --git a/crates/theme/src/themes/rose_pine.rs b/crates/theme/src/themes/rose_pine.rs index fe3bddb2d0d6bdbf8d031001a6ef90359a102250..f654e4d9953cb5418eb3838b3ce27b8691a92913 100644 --- a/crates/theme/src/themes/rose_pine.rs +++ b/crates/theme/src/themes/rose_pine.rs @@ -70,7 +70,7 @@ pub fn rose_pine() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x1d1b2aff).into()), editor_line_number: Some(rgba(0xe0def459).into()), editor_active_line_number: Some(rgba(0xe0def4ff).into()), - editor_invisible: Some(rgba(0x75718eff).into()), + editor_invisible: Some(rgba(0x28253cff).into()), editor_wrap_guide: Some(rgba(0xe0def40d).into()), editor_active_wrap_guide: Some(rgba(0xe0def41a).into()), editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()), @@ -528,7 +528,7 @@ pub fn rose_pine() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xfef9f2ff).into()), editor_line_number: Some(rgba(0x57527959).into()), editor_active_line_number: Some(rgba(0x575279ff).into()), - editor_invisible: Some(rgba(0x706c8cff).into()), + editor_invisible: Some(rgba(0x9691a4ff).into()), editor_wrap_guide: Some(rgba(0x5752790d).into()), editor_active_wrap_guide: Some(rgba(0x5752791a).into()), editor_document_highlight_read_background: Some(rgba(0x57949f1a).into()), @@ -986,7 +986,7 @@ pub fn rose_pine() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x28253cff).into()), editor_line_number: Some(rgba(0xe0def459).into()), editor_active_line_number: Some(rgba(0xe0def4ff).into()), - editor_invisible: Some(rgba(0x85819eff).into()), + editor_invisible: Some(rgba(0x595571ff).into()), editor_wrap_guide: Some(rgba(0xe0def40d).into()), editor_active_wrap_guide: Some(rgba(0xe0def41a).into()), editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()), diff --git a/crates/theme/src/themes/sandcastle.rs b/crates/theme/src/themes/sandcastle.rs index bace9e936f8c26364cc57151b8bbc81c5cd21cc4..ccf49061014ccf4dbfe4c71393aa9d433e1f63f3 100644 --- a/crates/theme/src/themes/sandcastle.rs +++ b/crates/theme/src/themes/sandcastle.rs @@ -69,7 +69,7 @@ pub fn sandcastle() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x2b3039ff).into()), editor_line_number: Some(rgba(0xfdf4c159).into()), editor_active_line_number: Some(rgba(0xfdf4c1ff).into()), - editor_invisible: Some(rgba(0xa69782ff).into()), + editor_invisible: Some(rgba(0x7c6f64ff).into()), editor_wrap_guide: Some(rgba(0xfdf4c10d).into()), editor_active_wrap_guide: Some(rgba(0xfdf4c11a).into()), editor_document_highlight_read_background: Some(rgba(0x528b8b1a).into()), diff --git a/crates/theme/src/themes/solarized.rs b/crates/theme/src/themes/solarized.rs index 8d4d7e1aa1c253ed3e3236ea90f6bbe8b4fd5e00..b903d645396e78c9a2a3a1ebfe0c0f714b59e7d5 100644 --- a/crates/theme/src/themes/solarized.rs +++ b/crates/theme/src/themes/solarized.rs @@ -70,7 +70,7 @@ pub fn solarized() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x04313cff).into()), editor_line_number: Some(rgba(0xfdf6e359).into()), editor_active_line_number: Some(rgba(0xfdf6e3ff).into()), - editor_invisible: Some(rgba(0x93a1a1ff).into()), + editor_invisible: Some(rgba(0x6d8288ff).into()), editor_wrap_guide: Some(rgba(0xfdf6e30d).into()), editor_active_wrap_guide: Some(rgba(0xfdf6e31a).into()), editor_document_highlight_read_background: Some(rgba(0x288bd11a).into()), @@ -514,7 +514,7 @@ pub fn solarized() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0xf3eddaff).into()), editor_line_number: Some(rgba(0x002b3659).into()), editor_active_line_number: Some(rgba(0x002b36ff).into()), - editor_invisible: Some(rgba(0x34555eff).into()), + editor_invisible: Some(rgba(0x6d8288ff).into()), editor_wrap_guide: Some(rgba(0x002b360d).into()), editor_active_wrap_guide: Some(rgba(0x002b361a).into()), editor_document_highlight_read_background: Some(rgba(0x298bd11a).into()), diff --git a/crates/theme/src/themes/summercamp.rs b/crates/theme/src/themes/summercamp.rs index 2ee5f12394dd00328a2d2c698d44687aeeab94b6..0a580e6893578d846b475493897e294aafaf74eb 100644 --- a/crates/theme/src/themes/summercamp.rs +++ b/crates/theme/src/themes/summercamp.rs @@ -69,7 +69,7 @@ pub fn summercamp() -> UserThemeFamily { editor_highlighted_line_background: Some(rgba(0x231f16ff).into()), editor_line_number: Some(rgba(0xf8f5de59).into()), editor_active_line_number: Some(rgba(0xf8f5deff).into()), - editor_invisible: Some(rgba(0x736e55ff).into()), + editor_invisible: Some(rgba(0x494433ff).into()), editor_wrap_guide: Some(rgba(0xf8f5de0d).into()), editor_active_wrap_guide: Some(rgba(0xf8f5de1a).into()), editor_document_highlight_read_background: Some(rgba(0x499bef1a).into()), diff --git a/crates/theme_importer/src/zed1/converter.rs b/crates/theme_importer/src/zed1/converter.rs index 9f40c3695fcc6773e549d829e84a922aceef567c..2f640c799f99ddf21989890c2e7a37a2a332b791 100644 --- a/crates/theme_importer/src/zed1/converter.rs +++ b/crates/theme_importer/src/zed1/converter.rs @@ -240,7 +240,7 @@ impl Zed1ThemeConverter { editor_highlighted_line_background: convert(editor.highlighted_line_background), editor_line_number: convert(editor.line_number), editor_active_line_number: convert(editor.line_number_active), - editor_invisible: convert(highest.variant.default.foreground), // TODO: Is this light enough? + editor_invisible: convert(editor.whitespace), editor_wrap_guide: convert(editor.wrap_guide), editor_active_wrap_guide: convert(editor.active_wrap_guide), editor_document_highlight_read_background: convert( From dd6e2df2a1b41d00e4e58c8515a55b0d55be9f44 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 11 Jan 2024 17:22:24 +0200 Subject: [PATCH 121/334] Show abs path matches in file finder --- Cargo.lock | 1 + crates/file_finder/Cargo.toml | 1 + crates/file_finder/src/file_finder.rs | 125 +++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 48c17f867223afd4c70a75dc24eea85a68799291..20394be5bc66dc8c544d4a7394bb8edc7429bd92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2522,6 +2522,7 @@ dependencies = [ name = "file_finder" version = "0.1.0" dependencies = [ + "anyhow", "collections", "ctor", "editor", diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 269d5790bc666fa12c7ef9a31d91356458b36db9..e80e310c707895352184ba903e146359922dd831 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -23,6 +23,7 @@ theme = { path = "../theme" } ui = { path = "../ui" } workspace = { path = "../workspace" } postage.workspace = true +anyhow.workspace = true serde.workspace = true [dev-dependencies] diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 0fe36084c2abda205fc873ef46b7eb7afd062491..ed3e57ba28a0bfbebbcac1f8aff3482c953be276 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -519,6 +519,62 @@ impl FileFinderDelegate { (file_name, file_name_positions, full_path, path_positions) } + + fn lookup_absolute_path( + &self, + query: PathLikeWithPosition, + cx: &mut ViewContext<'_, Picker>, + ) -> Task<()> { + cx.spawn(|picker, mut cx| async move { + let Some((project, fs)) = picker + .update(&mut cx, |picker, cx| { + let fs = Arc::clone(&picker.delegate.project.read(cx).fs()); + (picker.delegate.project.clone(), fs) + }) + .log_err() + else { + return; + }; + + let query_path = Path::new(query.path_like.path_query()); + let mut path_matches = Vec::new(); + match fs.metadata(query_path).await.log_err() { + Some(Some(_metadata)) => { + let update_result = project + .update(&mut cx, |project, cx| { + if let Some((worktree, relative_path)) = + project.find_local_worktree(query_path, cx) + { + path_matches.push(PathMatch { + score: 0.0, + positions: Vec::new(), + worktree_id: worktree.read(cx).id().to_usize(), + path: Arc::from(relative_path), + path_prefix: "".into(), + distance_to_relative_ancestor: usize::MAX, + }); + } + }) + .log_err(); + if update_result.is_none() { + return; + } + } + Some(None) => {} + None => return, + } + + picker + .update(&mut cx, |picker, cx| { + let picker_delegate = &mut picker.delegate; + let search_id = util::post_inc(&mut picker_delegate.search_count); + picker_delegate.set_search_matches(search_id, false, query, path_matches, cx); + + anyhow::Ok(()) + }) + .log_err(); + }) + } } impl PickerDelegate for FileFinderDelegate { @@ -588,7 +644,12 @@ impl PickerDelegate for FileFinderDelegate { }) }) .expect("infallible"); - self.spawn_search(query, cx) + + if Path::new(query.path_like.path_query()).is_absolute() { + self.lookup_absolute_path(query, cx) + } else { + self.spawn_search(query, cx) + } } } @@ -818,6 +879,68 @@ mod tests { } } + #[gpui::test] + async fn test_absolute_paths(cx: &mut TestAppContext) { + let app_state = init_test(cx); + app_state + .fs + .as_fake() + .insert_tree( + "/root", + json!({ + "a": { + "file1.txt": "", + "b": { + "file2.txt": "", + }, + } + }), + ) + .await; + + let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; + + let (picker, workspace, cx) = build_find_picker(project, cx); + + let matching_abs_path = "/root/a/b/file2.txt"; + picker + .update(cx, |picker, cx| { + picker + .delegate + .update_matches(matching_abs_path.to_string(), cx) + }) + .await; + picker.update(cx, |picker, _| { + assert_eq!( + collect_search_results(picker), + vec![PathBuf::from("a/b/file2.txt")], + "Matching abs path should be the only match" + ) + }); + cx.dispatch_action(SelectNext); + cx.dispatch_action(Confirm); + cx.read(|cx| { + let active_editor = workspace.read(cx).active_item_as::(cx).unwrap(); + assert_eq!(active_editor.read(cx).title(cx), "file2.txt"); + }); + + let mismatching_abs_path = "/root/a/b/file1.txt"; + picker + .update(cx, |picker, cx| { + picker + .delegate + .update_matches(mismatching_abs_path.to_string(), cx) + }) + .await; + picker.update(cx, |picker, _| { + assert_eq!( + collect_search_results(picker), + Vec::::new(), + "Mismatching abs path should produce no matches" + ) + }); + } + #[gpui::test] async fn test_complex_path(cx: &mut TestAppContext) { let app_state = init_test(cx); From cbbba41748092724bcf3d3eefe1667894d958ba8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 16:57:06 +0100 Subject: [PATCH 122/334] Reuse line layouts when reusing view --- crates/gpui/src/text_system.rs | 16 +++++-- crates/gpui/src/text_system/line_layout.rs | 51 +++++++++++++++++++--- crates/gpui/src/window.rs | 36 ++++++++------- 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 47073bcde0ef77b7b6674d0c0a24b7dfa107e948..093ef3a7d5d0537975381ea1086eea68bdaf47bd 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -9,11 +9,11 @@ pub use line_layout::*; pub use line_wrapper::*; use crate::{ - px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, - UnderlineStyle, + px, Bounds, DevicePixels, EntityId, Hsla, Pixels, PlatformTextSystem, Point, Result, + SharedString, Size, UnderlineStyle, }; use anyhow::anyhow; -use collections::FxHashMap; +use collections::{FxHashMap, FxHashSet}; use core::fmt; use itertools::Itertools; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; @@ -186,6 +186,10 @@ impl TextSystem { } } + pub fn with_view(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R { + self.line_layout_cache.with_view(view_id, f) + } + pub fn layout_line( &self, text: &str, @@ -361,7 +365,11 @@ impl TextSystem { } pub fn start_frame(&self) { - self.line_layout_cache.start_frame() + self.line_layout_cache.start_frame(); + } + + pub fn end_frame(&self, reused_views: &FxHashSet) { + self.line_layout_cache.end_frame(reused_views) } pub fn line_wrapper( diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 6506d7794c7b4693e61af6d278c06a191528f448..909f0513467a9f3f173e2edfeb489472fcd7afc8 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -1,5 +1,5 @@ -use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size}; -use collections::FxHashMap; +use crate::{px, EntityId, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size}; +use collections::{FxHashMap, FxHashSet}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::SmallVec; use std::{ @@ -236,6 +236,7 @@ impl WrappedLineLayout { } pub(crate) struct LineLayoutCache { + view_stack: Mutex>, previous_frame: Mutex>>, current_frame: RwLock>>, previous_frame_wrapped: Mutex>>, @@ -246,6 +247,7 @@ pub(crate) struct LineLayoutCache { impl LineLayoutCache { pub fn new(platform_text_system: Arc) -> Self { Self { + view_stack: Mutex::default(), previous_frame: Mutex::default(), current_frame: RwLock::default(), previous_frame_wrapped: Mutex::default(), @@ -254,11 +256,43 @@ impl LineLayoutCache { } } - pub fn start_frame(&self) { + pub fn end_frame(&self, reused_views: &FxHashSet) { + debug_assert_eq!(self.view_stack.lock().len(), 0); + let mut prev_frame = self.previous_frame.lock(); let mut curr_frame = self.current_frame.write(); + for (key, layout) in prev_frame.drain() { + if key + .parent_view_id + .map_or(false, |view_id| reused_views.contains(&view_id)) + { + curr_frame.insert(key, layout); + } + } std::mem::swap(&mut *prev_frame, &mut *curr_frame); - curr_frame.clear(); + + let mut prev_frame_wrapped = self.previous_frame_wrapped.lock(); + let mut curr_frame_wrapped = self.current_frame_wrapped.write(); + for (key, layout) in prev_frame_wrapped.drain() { + if key + .parent_view_id + .map_or(false, |view_id| reused_views.contains(&view_id)) + { + curr_frame_wrapped.insert(key, layout); + } + } + std::mem::swap(&mut *prev_frame_wrapped, &mut *curr_frame_wrapped); + } + + pub fn with_view(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R { + self.view_stack.lock().push(view_id); + let result = f(); + self.view_stack.lock().pop(); + result + } + + fn parent_view_id(&self) -> Option { + self.view_stack.lock().last().copied() } pub fn layout_wrapped_line( @@ -273,6 +307,7 @@ impl LineLayoutCache { font_size, runs, wrap_width, + parent_view_id: self.parent_view_id(), } as &dyn AsCacheKeyRef; let current_frame = self.current_frame_wrapped.upgradable_read(); @@ -301,6 +336,7 @@ impl LineLayoutCache { font_size, runs: SmallVec::from(runs), wrap_width, + parent_view_id: self.parent_view_id(), }; current_frame.insert(key, layout.clone()); layout @@ -313,6 +349,7 @@ impl LineLayoutCache { font_size, runs, wrap_width: None, + parent_view_id: self.parent_view_id(), } as &dyn AsCacheKeyRef; let current_frame = self.current_frame.upgradable_read(); @@ -331,6 +368,7 @@ impl LineLayoutCache { font_size, runs: SmallVec::from(runs), wrap_width: None, + parent_view_id: self.parent_view_id(), }; current_frame.insert(key, layout.clone()); layout @@ -348,12 +386,13 @@ trait AsCacheKeyRef { fn as_cache_key_ref(&self) -> CacheKeyRef; } -#[derive(Eq)] +#[derive(Debug, Eq)] struct CacheKey { text: String, font_size: Pixels, runs: SmallVec<[FontRun; 1]>, wrap_width: Option, + parent_view_id: Option, } #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -362,6 +401,7 @@ struct CacheKeyRef<'a> { font_size: Pixels, runs: &'a [FontRun], wrap_width: Option, + parent_view_id: Option, } impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) { @@ -385,6 +425,7 @@ impl AsCacheKeyRef for CacheKey { font_size: self.font_size, runs: self.runs.as_slice(), wrap_width: self.wrap_width, + parent_view_id: self.parent_view_id, } } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7e38353e477d4002d4ee3dde58f1f86be3dba21e..00d42144c73755a7c2f6557a97d77adbbf7a67a3 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1461,14 +1461,11 @@ impl<'a> WindowContext<'a> { self.window.focus_invalidated = false; } - self.text_system().start_frame(); if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut() { requested_handler.handler = self.window.platform_window.take_input_handler(); } - self.window.layout_engine.as_mut().unwrap().clear(); - self.window.next_frame.clear(); let root_view = self.window.root_view.take().unwrap(); self.with_z_index(0, |cx| { @@ -1528,11 +1525,6 @@ impl<'a> WindowContext<'a> { self.platform.set_cursor_style(cursor_style); } - self.window - .next_frame - .reuse_views(&mut self.window.rendered_frame); - self.window.next_frame.scene.finish(); - // Register requested input handler with the platform window. if let Some(requested_input) = self.window.next_frame.requested_input_handler.as_mut() { if let Some(handler) = requested_input.handler.take() { @@ -1540,16 +1532,25 @@ impl<'a> WindowContext<'a> { } } + self.window + .next_frame + .reuse_views(&mut self.window.rendered_frame); + self.window.next_frame.scene.finish(); + self.window.layout_engine.as_mut().unwrap().clear(); + self.text_system() + .end_frame(&self.window.next_frame.reused_views); + ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); + + self.window.refreshing = false; + self.window.drawing = false; + let previous_focus_path = self.window.rendered_frame.focus_path(); let previous_window_active = self.window.rendered_frame.window_active; mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); + self.window.next_frame.clear(); let current_focus_path = self.window.rendered_frame.focus_path(); let current_window_active = self.window.rendered_frame.window_active; - self.window.refreshing = false; - self.window.drawing = false; - ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); - if previous_focus_path != current_focus_path || previous_window_active != current_window_active { @@ -2005,10 +2006,13 @@ impl<'a> WindowContext<'a> { view_id: EntityId, f: impl FnOnce(&mut Self) -> R, ) -> R { - self.window.next_frame.view_stack.push(view_id); - let result = f(self); - self.window.next_frame.view_stack.pop(); - result + let text_system = self.text_system().clone(); + text_system.with_view(view_id, || { + self.window.next_frame.view_stack.push(view_id); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + }) } pub(crate) fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { From 101cedb5f7e1cbe86e8340a428870081c59c39a5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 16:59:33 +0100 Subject: [PATCH 123/334] :lipstick: --- crates/gpui/src/text_system.rs | 8 ++------ crates/gpui/src/text_system/line_layout.rs | 2 +- crates/gpui/src/window.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 093ef3a7d5d0537975381ea1086eea68bdaf47bd..7c5adef12d7de60c3a87c7280a41d9fd4c5df5bf 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -364,12 +364,8 @@ impl TextSystem { Ok(lines) } - pub fn start_frame(&self) { - self.line_layout_cache.start_frame(); - } - - pub fn end_frame(&self, reused_views: &FxHashSet) { - self.line_layout_cache.end_frame(reused_views) + pub fn finish_frame(&self, reused_views: &FxHashSet) { + self.line_layout_cache.finish_frame(reused_views) } pub fn line_wrapper( diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 909f0513467a9f3f173e2edfeb489472fcd7afc8..6c466f9680e8ec8ff75812bcdf15a172c9ff6258 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -256,7 +256,7 @@ impl LineLayoutCache { } } - pub fn end_frame(&self, reused_views: &FxHashSet) { + pub fn finish_frame(&self, reused_views: &FxHashSet) { debug_assert_eq!(self.view_stack.lock().len(), 0); let mut prev_frame = self.previous_frame.lock(); diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 00d42144c73755a7c2f6557a97d77adbbf7a67a3..02683d655da04b57210a3bef6f99f6c85ab75873 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -361,7 +361,7 @@ impl Frame { .unwrap_or_default() } - fn reuse_views(&mut self, prev_frame: &mut Self) { + fn finish(&mut self, prev_frame: &mut Self) { // Reuse mouse listeners that didn't change since the last frame. for (type_id, listeners) in &mut prev_frame.mouse_listeners { let next_listeners = self.mouse_listeners.entry(*type_id).or_default(); @@ -390,6 +390,7 @@ impl Frame { // Reuse geometry that didn't change since the last frame. self.scene .reuse_views(&self.reused_views, &mut prev_frame.scene); + self.scene.finish(); } } @@ -1532,13 +1533,12 @@ impl<'a> WindowContext<'a> { } } - self.window - .next_frame - .reuse_views(&mut self.window.rendered_frame); - self.window.next_frame.scene.finish(); self.window.layout_engine.as_mut().unwrap().clear(); self.text_system() - .end_frame(&self.window.next_frame.reused_views); + .finish_frame(&self.window.next_frame.reused_views); + self.window + .next_frame + .finish(&mut self.window.rendered_frame); ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); self.window.refreshing = false; From f4c698ba27d2069f7aa952111cd4e3664844bccb Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 11:24:55 -0500 Subject: [PATCH 124/334] Fix bug with improperly reported environment When logging the edit environment, we were logging the newest environment being sent into the EventCoalescer on the latest activity log, when we should've been logging the environment that was associated with the ended period within the EventCoalescer. --- crates/client/src/telemetry.rs | 4 +- .../client/src/telemetry/event_coalescer.rs | 83 +++++++++++-------- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 32ebaad3bdbadccd51a2c0520307898cd729cadd..7ce4b6e4f2ab10f9deed046917bd28ddbbd2d1e1 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -404,10 +404,10 @@ impl Telemetry { pub fn log_edit_event(self: &Arc, environment: &'static str) { let mut state = self.state.lock(); - let coalesced_duration = state.event_coalescer.log_event(environment); + let period_data = state.event_coalescer.log_event(environment); drop(state); - if let Some((start, end)) = coalesced_duration { + if let (Some((start, end)), Some(environment)) = period_data { let event = Event::Edit { duration: end.timestamp_millis() - start.timestamp_millis(), environment, diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 96c61486b866c4521fe27eb8d9f3531cd5117661..4e6ddcbe7b2372e368e499dfb1a019c456907fa5 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -22,7 +22,7 @@ impl EventCoalescer { pub fn log_event( &mut self, environment: &'static str, - ) -> Option<(DateTime, DateTime)> { + ) -> (Option<(DateTime, DateTime)>, Option<&'static str>) { self.log_event_with_time(Utc::now(), environment) } @@ -34,13 +34,13 @@ impl EventCoalescer { &mut self, log_time: DateTime, environment: &'static str, - ) -> Option<(DateTime, DateTime)> { + ) -> (Option<(DateTime, DateTime)>, Option<&'static str>) { let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap(); let Some(period_start) = self.period_start else { self.period_start = Some(log_time); self.environment = Some(environment); - return None; + return (None, self.environment); }; let period_end = self @@ -51,18 +51,24 @@ impl EventCoalescer { let should_coaelesce = !within_timeout || !environment_is_same; if should_coaelesce { + let previous_environment = self.environment; + self.period_start = Some(log_time); self.period_end = None; self.environment = Some(environment); - return Some(( - period_start, - if within_timeout { log_time } else { period_end }, - )); + + return ( + Some(( + period_start, + if within_timeout { log_time } else { period_end }, + )), + previous_environment, + ); } self.period_end = Some(log_time); - None + (None, self.environment) } } @@ -82,9 +88,9 @@ mod tests { assert_eq!(event_coalescer.environment, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); - let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -95,9 +101,9 @@ mod tests { // Ensure that many calls within the timeout don't start a new period for _ in 0..100 { period_end += within_timeout_adjustment; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_1); + let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, Some(period_end)); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -106,10 +112,12 @@ mod tests { let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); // Logging an event exceeding the timeout should start a new period let new_period_start = period_end + exceed_timeout_adjustment; - let coalesced_duration = - event_coalescer.log_event_with_time(new_period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(new_period_start, environment_1); - assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!( + period_data, + (Some((period_start, period_end)), Some(environment_1)) + ); assert_eq!(event_coalescer.period_start, Some(new_period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -125,18 +133,18 @@ mod tests { assert_eq!(event_coalescer.environment, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); - let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let period_end = period_start + within_timeout_adjustment; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_1); + let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, Some(period_end)); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -144,9 +152,12 @@ mod tests { // Logging an event within the timeout but with a different environment should start a new period let period_end = period_end + within_timeout_adjustment; let environment_2 = "environment_2"; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + let period_data = event_coalescer.log_event_with_time(period_end, environment_2); - assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!( + period_data, + (Some((period_start, period_end)), Some(environment_1)) + ); assert_eq!(event_coalescer.period_start, Some(period_end)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_2)); @@ -162,9 +173,9 @@ mod tests { assert_eq!(event_coalescer.environment, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); - let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -172,9 +183,12 @@ mod tests { let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let period_end = period_start + within_timeout_adjustment; let environment_2 = "environment_2"; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + let period_data = event_coalescer.log_event_with_time(period_end, environment_2); - assert_eq!(coalesced_duration, Some((period_start, period_end))); + assert_eq!( + period_data, + (Some((period_start, period_end)), Some(environment_1)) + ); assert_eq!(event_coalescer.period_start, Some(period_end)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_2)); @@ -196,9 +210,9 @@ mod tests { assert_eq!(event_coalescer.environment, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); - let coalesced_duration = event_coalescer.log_event_with_time(period_start, environment_1); + let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(coalesced_duration, None); + assert_eq!(period_data, (None, Some(environment_1))); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -206,14 +220,17 @@ mod tests { let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); let period_end = period_start + exceed_timeout_adjustment; let environment_2 = "environment_2"; - let coalesced_duration = event_coalescer.log_event_with_time(period_end, environment_2); + let period_data = event_coalescer.log_event_with_time(period_end, environment_2); assert_eq!( - coalesced_duration, - Some(( - period_start, - period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT - )) + period_data, + ( + Some(( + period_start, + period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT + )), + Some(environment_1) + ) ); assert_eq!(event_coalescer.period_start, Some(period_end)); assert_eq!(event_coalescer.period_end, None); From 76955f6a5ddb5483b09bbea24bb240a65ac53165 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 11 Jan 2024 11:25:35 -0500 Subject: [PATCH 125/334] Stop following on escape key press --- assets/keymaps/default.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 3ff0db1a160e4d420a7aad83d56404e313ad3b46..1372f7a4157da475aa8ccbff8aeb940c85d2ea79 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -412,7 +412,8 @@ "cmd-shift-e": "project_panel::ToggleFocus", "cmd-?": "assistant::ToggleFocus", "cmd-alt-s": "workspace::SaveAll", - "cmd-k m": "language_selector::Toggle" + "cmd-k m": "language_selector::Toggle", + "escape": "workspace::Unfollow" } }, // Bindings from Sublime Text From 06ce1af5306b5a1f35de80613ed458cc9655b247 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 11:30:22 -0500 Subject: [PATCH 126/334] Only return environment when period ends --- crates/client/src/telemetry/event_coalescer.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 4e6ddcbe7b2372e368e499dfb1a019c456907fa5..9b3bf04c409893613e3fc4a40da5a4a5711b1e3a 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -40,7 +40,7 @@ impl EventCoalescer { let Some(period_start) = self.period_start else { self.period_start = Some(log_time); self.environment = Some(environment); - return (None, self.environment); + return (None, None); }; let period_end = self @@ -68,7 +68,7 @@ impl EventCoalescer { self.period_end = Some(log_time); - (None, self.environment) + (None, None) } } @@ -90,7 +90,7 @@ mod tests { let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -103,7 +103,7 @@ mod tests { period_end += within_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, Some(period_end)); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -135,7 +135,7 @@ mod tests { let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -144,7 +144,7 @@ mod tests { let period_end = period_start + within_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, Some(period_end)); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -175,7 +175,7 @@ mod tests { let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); @@ -212,7 +212,7 @@ mod tests { let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, Some(environment_1))); + assert_eq!(period_data, (None, None)); assert_eq!(event_coalescer.period_start, Some(period_start)); assert_eq!(event_coalescer.period_end, None); assert_eq!(event_coalescer.environment, Some(environment_1)); From 11b433dc1cfee216796f36766f3b7ba492998982 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Jan 2024 18:24:07 +0100 Subject: [PATCH 127/334] Move back to sorting entries in the depth map as we insert them --- crates/gpui/src/window.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 02683d655da04b57210a3bef6f99f6c85ab75873..c11f12d89186ed91ffd41a1dcbc2b12189f73cc1 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -375,10 +375,14 @@ impl Frame { // Reuse entries in the depth map that didn't change since the last frame. for (order, view_id, bounds) in prev_frame.depth_map.drain(..) { if self.reused_views.contains(&view_id) { - self.depth_map.push((order, view_id, bounds)); + match self + .depth_map + .binary_search_by(|(level, _, _)| order.cmp(level)) + { + Ok(i) | Err(i) => self.depth_map.insert(i, (order, view_id, bounds)), + } } } - self.depth_map.sort_by(|a, b| a.0.cmp(&b.0)); // Retain element states for views that didn't change since the last frame. for (element_id, state) in prev_frame.element_states.drain() { @@ -1052,10 +1056,10 @@ impl<'a> WindowContext<'a> { pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); let view_id = self.parent_view_id(); - self.window - .next_frame - .depth_map - .push((stacking_order, view_id, bounds)); + let depth_map = &mut self.window.next_frame.depth_map; + match depth_map.binary_search_by(|(level, _, _)| stacking_order.cmp(level)) { + Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, view_id, bounds)), + } } /// Returns true if there is no opaque layer containing the given point From a9fce19048e0ebfd1b113da89e0e41747abcbd54 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 11 Jan 2024 12:27:59 -0500 Subject: [PATCH 128/334] Return a single Option from EventCoalescer --- crates/client/src/telemetry.rs | 2 +- .../client/src/telemetry/event_coalescer.rs | 228 ++++++++++-------- 2 files changed, 132 insertions(+), 98 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 7ce4b6e4f2ab10f9deed046917bd28ddbbd2d1e1..aa0c7c4af58be6c181a04e2b73cff55230096bf9 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -407,7 +407,7 @@ impl Telemetry { let period_data = state.event_coalescer.log_event(environment); drop(state); - if let (Some((start, end)), Some(environment)) = period_data { + if let Some((start, end, environment)) = period_data { let event = Event::Edit { duration: end.timestamp_millis() - start.timestamp_millis(), environment, diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 9b3bf04c409893613e3fc4a40da5a4a5711b1e3a..f0efeb38e6685c94bfb2da7395a91ef168f41cd9 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -4,25 +4,26 @@ use std::time; const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20); const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from_millis(1); +#[derive(Debug, PartialEq)] +struct PeriodData { + environment: &'static str, + start: DateTime, + end: Option>, +} + pub struct EventCoalescer { - environment: Option<&'static str>, - period_start: Option>, - period_end: Option>, + state: Option, } impl EventCoalescer { pub fn new() -> Self { - Self { - environment: None, - period_start: None, - period_end: None, - } + Self { state: None } } pub fn log_event( &mut self, environment: &'static str, - ) -> (Option<(DateTime, DateTime)>, Option<&'static str>) { + ) -> Option<(DateTime, DateTime, &'static str)> { self.log_event_with_time(Utc::now(), environment) } @@ -34,41 +35,43 @@ impl EventCoalescer { &mut self, log_time: DateTime, environment: &'static str, - ) -> (Option<(DateTime, DateTime)>, Option<&'static str>) { + ) -> Option<(DateTime, DateTime, &'static str)> { let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap(); - let Some(period_start) = self.period_start else { - self.period_start = Some(log_time); - self.environment = Some(environment); - return (None, None); + let Some(state) = &mut self.state else { + self.state = Some(PeriodData { + start: log_time, + end: None, + environment, + }); + return None; }; - let period_end = self - .period_end - .unwrap_or(period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT); + let period_end = state + .end + .unwrap_or(state.start + SIMULATED_DURATION_FOR_SINGLE_EVENT); let within_timeout = log_time - period_end < coalesce_timeout; - let environment_is_same = self.environment == Some(environment); + let environment_is_same = state.environment == environment; let should_coaelesce = !within_timeout || !environment_is_same; if should_coaelesce { - let previous_environment = self.environment; + let previous_environment = state.environment; + let original_start = state.start; - self.period_start = Some(log_time); - self.period_end = None; - self.environment = Some(environment); + state.start = log_time; + state.end = None; + state.environment = environment; - return ( - Some(( - period_start, - if within_timeout { log_time } else { period_end }, - )), + return Some(( + original_start, + if within_timeout { log_time } else { period_end }, previous_environment, - ); + )); } - self.period_end = Some(log_time); + state.end = Some(log_time); - (None, None) + None } } @@ -83,17 +86,20 @@ mod tests { let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(); - assert_eq!(event_coalescer.period_start, None); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, None); + assert_eq!(event_coalescer.state, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: None, + environment: environment_1, + }) + ); let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let mut period_end = period_start; @@ -103,10 +109,15 @@ mod tests { period_end += within_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, Some(period_end)); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: Some(period_end), + environment: environment_1, + }) + ); } let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); @@ -114,13 +125,15 @@ mod tests { let new_period_start = period_end + exceed_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(new_period_start, environment_1); + assert_eq!(period_data, Some((period_start, period_end, environment_1))); assert_eq!( - period_data, - (Some((period_start, period_end)), Some(environment_1)) + event_coalescer.state, + Some(PeriodData { + start: new_period_start, + end: None, + environment: environment_1, + }) ); - assert_eq!(event_coalescer.period_start, Some(new_period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); } #[test] @@ -128,39 +141,49 @@ mod tests { let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(); - assert_eq!(event_coalescer.period_start, None); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, None); + assert_eq!(event_coalescer.state, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: None, + environment: environment_1, + }) + ); let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let period_end = period_start + within_timeout_adjustment; let period_data = event_coalescer.log_event_with_time(period_end, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, Some(period_end)); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: Some(period_end), + environment: environment_1, + }) + ); // Logging an event within the timeout but with a different environment should start a new period let period_end = period_end + within_timeout_adjustment; let environment_2 = "environment_2"; let period_data = event_coalescer.log_event_with_time(period_end, environment_2); + assert_eq!(period_data, Some((period_start, period_end, environment_1))); assert_eq!( - period_data, - (Some((period_start, period_end)), Some(environment_1)) + event_coalescer.state, + Some(PeriodData { + start: period_end, + end: None, + environment: environment_2, + }) ); - assert_eq!(event_coalescer.period_start, Some(period_end)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_2)); } #[test] @@ -168,54 +191,62 @@ mod tests { let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(); - assert_eq!(event_coalescer.period_start, None); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, None); + assert_eq!(event_coalescer.state, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: None, + environment: environment_1, + }) + ); let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); let period_end = period_start + within_timeout_adjustment; let environment_2 = "environment_2"; let period_data = event_coalescer.log_event_with_time(period_end, environment_2); + assert_eq!(period_data, Some((period_start, period_end, environment_1))); assert_eq!( - period_data, - (Some((period_start, period_end)), Some(environment_1)) + event_coalescer.state, + Some(PeriodData { + start: period_end, + end: None, + environment: environment_2, + }) ); - assert_eq!(event_coalescer.period_start, Some(period_end)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_2)); } - // 0 20 40 60 - // |-------------------|-------------------|-------------------|------------------- - // |--------|----------env change - // |------------------- - // |period_start |period_end - // |new_period_start + // // 0 20 40 60 + // // |-------------------|-------------------|-------------------|------------------- + // // |--------|----------env change + // // |------------------- + // // |period_start |period_end + // // |new_period_start #[test] fn test_switching_environment_while_exceeding_timeout() { let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(); - assert_eq!(event_coalescer.period_start, None); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, None); + assert_eq!(event_coalescer.state, None); let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); let period_data = event_coalescer.log_event_with_time(period_start, environment_1); - assert_eq!(period_data, (None, None)); - assert_eq!(event_coalescer.period_start, Some(period_start)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_1)); + assert_eq!(period_data, None); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_start, + end: None, + environment: environment_1, + }) + ); let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); let period_end = period_start + exceed_timeout_adjustment; @@ -224,17 +255,20 @@ mod tests { assert_eq!( period_data, - ( - Some(( - period_start, - period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT - )), - Some(environment_1) - ) + Some(( + period_start, + period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT, + environment_1 + )) + ); + assert_eq!( + event_coalescer.state, + Some(PeriodData { + start: period_end, + end: None, + environment: environment_2, + }) ); - assert_eq!(event_coalescer.period_start, Some(period_end)); - assert_eq!(event_coalescer.period_end, None); - assert_eq!(event_coalescer.environment, Some(environment_2)); } // 0 20 40 60 // |-------------------|-------------------|-------------------|------------------- From 06493471aa2621aa5629acb051c46249378d470a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 10:35:20 -0700 Subject: [PATCH 129/334] Guard against infinite loop in focus handling --- crates/gpui/src/window.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 25bfa799d2d2cdd6b3ae72d2607dbf53fe0e03dd..509a6d8466609b5041f4f958ca1676107b639a14 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1429,9 +1429,6 @@ impl<'a> WindowContext<'a> { self.platform.set_cursor_style(cursor_style); } - self.window.drawing = false; - ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); - if previous_focus_path != current_focus_path || previous_window_active != current_window_active { @@ -1460,6 +1457,9 @@ impl<'a> WindowContext<'a> { .retain(&(), |listener| listener(&event, self)); } + self.window.drawing = false; + ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); + scene } From 634a55257d2c16484c34673f3cff61cf91f456a2 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:55:39 +0100 Subject: [PATCH 130/334] project search: Do not open a new existing item in the current pane for DeploySearch Fixes https://github.com/zed-industries/community/issues/2395 using the first approach suggested in the original post (focus the existing search without bringing it over to a pane). --- crates/search/src/project_search.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 6fd66b5bad2c1c9ab1036a445f1e2061b791f206..8244c1cf2837a787b5800cf7f195a70cfebb2078 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1016,8 +1016,6 @@ impl ProjectSearchView { view }; - workspace.add_item(Box::new(search.clone()), cx); - search.update(cx, |search, cx| { if let Some(query) = query { search.set_query(&query, cx); From af790d11ee30491043a889cf951c1efe587ef1ac Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:20:10 +0100 Subject: [PATCH 131/334] Add test for new DeploySearch behaviour --- crates/search/src/project_search.rs | 120 +++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8244c1cf2837a787b5800cf7f195a70cfebb2078..8897ae4bcfcd2ec2f14266dac81d97e8cf161985 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1015,7 +1015,6 @@ impl ProjectSearchView { workspace.add_item(Box::new(view.clone()), cx); view }; - search.update(cx, |search, cx| { if let Some(query) = query { search.set_query(&query, cx); @@ -1982,6 +1981,7 @@ pub mod tests { use semantic_index::semantic_index_settings::SemanticIndexSettings; use serde_json::json; use settings::{Settings, SettingsStore}; + use workspace::DeploySearch; #[gpui::test] async fn test_project_search(cx: &mut TestAppContext) { @@ -3109,6 +3109,124 @@ pub mod tests { .unwrap(); } + #[gpui::test] + async fn test_deploy_search_with_multiple_panes(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": "const ONE: usize = 1;", + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let worktree_id = project.update(cx, |this, cx| { + this.worktrees().next().unwrap().read(cx).id() + }); + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let panes: Vec<_> = window + .update(cx, |this, _| this.panes().to_owned()) + .unwrap(); + assert_eq!(panes.len(), 1); + let first_pane = panes.get(0).cloned().unwrap(); + assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 0); + window + .update(cx, |workspace, cx| { + workspace.open_path( + (worktree_id, "one.rs"), + Some(first_pane.downgrade()), + true, + cx, + ) + }) + .unwrap() + .await + .unwrap(); + assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1); + let second_pane = window + .update(cx, |workspace, cx| { + workspace.split_and_clone(first_pane.clone(), workspace::SplitDirection::Right, cx) + }) + .unwrap() + .unwrap(); + assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 1); + assert!(window + .update(cx, |_, cx| second_pane + .focus_handle(cx) + .contains_focused(cx)) + .unwrap()); + let search_bar = window.build_view(cx, |_| ProjectSearchBar::new()); + window + .update(cx, { + let search_bar = search_bar.clone(); + let pane = first_pane.clone(); + move |workspace, cx| { + assert_eq!(workspace.panes().len(), 2); + pane.update(cx, move |pane, cx| { + pane.toolbar() + .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) + }); + } + }) + .unwrap(); + window + .update(cx, { + let search_bar = search_bar.clone(); + let pane = second_pane.clone(); + move |workspace, cx| { + assert_eq!(workspace.panes().len(), 2); + pane.update(cx, move |pane, cx| { + pane.toolbar() + .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) + }); + + ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) + } + }) + .unwrap(); + + cx.run_until_parked(); + assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 2); + assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1); + window + .update(cx, |workspace, cx| { + assert_eq!(workspace.active_pane(), &second_pane); + second_pane.update(cx, |this, cx| { + assert_eq!(this.active_item_index(), 1); + this.activate_prev_item(false, cx); + assert_eq!(this.active_item_index(), 0); + }); + workspace.activate_pane_in_direction(workspace::SplitDirection::Left, cx); + }) + .unwrap(); + window + .update(cx, |workspace, cx| { + assert_eq!(workspace.active_pane(), &first_pane); + assert_eq!(first_pane.read(cx).items_len(), 1); + assert_eq!(second_pane.read(cx).items_len(), 2); + }) + .unwrap(); + cx.dispatch_action(window.into(), DeploySearch); + + // We should have same # of items in workspace, the only difference being that + // the search we've deployed previously should now be focused. + window + .update(cx, |workspace, cx| { + assert_eq!(workspace.active_pane(), &second_pane); + second_pane.update(cx, |this, _| { + assert_eq!(this.active_item_index(), 1); + assert_eq!(this.items_len(), 2); + }); + first_pane.update(cx, |this, cx| { + assert!(!cx.focus_handle().contains_focused(cx)); + assert_eq!(this.items_len(), 1); + }); + }) + .unwrap(); + } + pub fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { let settings = SettingsStore::test(cx); From a98d0489053ca2053c5a6e3b0673f79e3bedd053 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 18:52:00 +0100 Subject: [PATCH 132/334] gpui: Make TextSystem::line_wrapper non-fallible. (#4022) Editors WrapMap could become desynchronised if user had an invalid font specified in their config. Compared to Zed1, WrapMap ignored the resolution failure instead of panicking. Now, if there's an invalid font in the user config, we just fall back to an arbitrary default. Release Notes: - Fixed the editor panic in presence of invalid font name in the config (fixes https://github.com/zed-industries/community/issues/2397) --------- Co-authored-by: Conrad Co-authored-by: Conrad Irwin --- crates/editor/src/display_map/wrap_map.rs | 52 ++++++++++------------- crates/gpui/src/text_system.rs | 22 +++------- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index dbd58b0accd89c4054d266f4867659add671a03b..ce2e5ee3d9eb66c1e4a769dacdb9ff8c357a1ff3 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -11,7 +11,6 @@ use smol::future::yield_now; use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration}; use sum_tree::{Bias, Cursor, SumTree}; use text::Patch; -use util::ResultExt; pub use super::tab_map::TextSummary; pub type WrapEdit = text::Edit; @@ -154,26 +153,24 @@ impl WrapMap { if let Some(wrap_width) = self.wrap_width { let mut new_snapshot = self.snapshot.clone(); - let mut edits = Patch::default(); + let text_system = cx.text_system().clone(); let (font, font_size) = self.font_with_size.clone(); let task = cx.background_executor().spawn(async move { - if let Some(mut line_wrapper) = text_system.line_wrapper(font, font_size).log_err() - { - let tab_snapshot = new_snapshot.tab_snapshot.clone(); - let range = TabPoint::zero()..tab_snapshot.max_point(); - edits = new_snapshot - .update( - tab_snapshot, - &[TabEdit { - old: range.clone(), - new: range.clone(), - }], - wrap_width, - &mut line_wrapper, - ) - .await; - } + let mut line_wrapper = text_system.line_wrapper(font, font_size); + let tab_snapshot = new_snapshot.tab_snapshot.clone(); + let range = TabPoint::zero()..tab_snapshot.max_point(); + let edits = new_snapshot + .update( + tab_snapshot, + &[TabEdit { + old: range.clone(), + new: range.clone(), + }], + wrap_width, + &mut line_wrapper, + ) + .await; (new_snapshot, edits) }); @@ -245,15 +242,12 @@ impl WrapMap { let (font, font_size) = self.font_with_size.clone(); let update_task = cx.background_executor().spawn(async move { let mut edits = Patch::default(); - if let Some(mut line_wrapper) = - text_system.line_wrapper(font, font_size).log_err() - { - for (tab_snapshot, tab_edits) in pending_edits { - let wrap_edits = snapshot - .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) - .await; - edits = edits.compose(&wrap_edits); - } + let mut line_wrapper = text_system.line_wrapper(font, font_size); + for (tab_snapshot, tab_edits) in pending_edits { + let wrap_edits = snapshot + .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) + .await; + edits = edits.compose(&wrap_edits); } (snapshot, edits) }); @@ -1059,7 +1053,7 @@ mod tests { }; let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); let font = font("Helvetica"); - let _font_id = text_system.font_id(&font).unwrap(); + let _font_id = text_system.font_id(&font); let font_size = px(14.0); log::info!("Tab size: {}", tab_size); @@ -1086,7 +1080,7 @@ mod tests { let tabs_snapshot = tab_map.set_max_expansion_column(32); log::info!("TabMap text: {:?}", tabs_snapshot.text()); - let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size).unwrap(); + let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size); let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 47073bcde0ef77b7b6674d0c0a24b7dfa107e948..2d3cc34f3fc09e6402ece055f9aa6ba45fe5434e 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -364,28 +364,20 @@ impl TextSystem { self.line_layout_cache.start_frame() } - pub fn line_wrapper( - self: &Arc, - font: Font, - font_size: Pixels, - ) -> Result { + pub fn line_wrapper(self: &Arc, font: Font, font_size: Pixels) -> LineWrapperHandle { let lock = &mut self.wrapper_pool.lock(); - let font_id = self.font_id(&font)?; + let font_id = self.resolve_font(&font); let wrappers = lock .entry(FontIdWithSize { font_id, font_size }) .or_default(); - let wrapper = wrappers.pop().map(anyhow::Ok).unwrap_or_else(|| { - Ok(LineWrapper::new( - font_id, - font_size, - self.platform_text_system.clone(), - )) - })?; + let wrapper = wrappers.pop().unwrap_or_else(|| { + LineWrapper::new(font_id, font_size, self.platform_text_system.clone()) + }); - Ok(LineWrapperHandle { + LineWrapperHandle { wrapper: Some(wrapper), text_system: self.clone(), - }) + } } pub fn raster_bounds(&self, params: &RenderGlyphParams) -> Result> { From 5feae86900ed7a2738fe79caa144388f56333f0c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 09:55:16 -0800 Subject: [PATCH 133/334] Avoid bright green separators when displaying untitled buffers in multi-buffers --- crates/editor/src/element.rs | 51 ++++++++++++++---------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7b33a3239d05e7a6e73aa072e3b946d057253d0f..7deda0ba46b1e7755ba9df6b4e84747caab28a7d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2265,11 +2265,9 @@ impl EditorElement { .map_or(range.context.start, |primary| primary.start); let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); - let jump_handler = cx.listener_for(&self.editor, move |editor, _, cx| { + cx.listener_for(&self.editor, move |editor, _, cx| { editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); - }); - - jump_handler + }) }); let element = if *starts_new_buffer { @@ -2349,34 +2347,25 @@ impl EditorElement { .text_color(cx.theme().colors().editor_line_number) .child("..."), ) - .map(|this| { - if let Some(jump_handler) = jump_handler { - this.child( - ButtonLike::new("jump to collapsed context") - .style(ButtonStyle::Transparent) - .full_width() - .on_click(jump_handler) - .tooltip(|cx| { - Tooltip::for_action( - "Jump to Buffer", - &OpenExcerpts, - cx, - ) - }) - .child( - div() - .h_px() - .w_full() - .bg(cx.theme().colors().border_variant) - .group_hover("", |style| { - style.bg(cx.theme().colors().border) - }), - ), + .child( + ButtonLike::new("jump to collapsed context") + .style(ButtonStyle::Transparent) + .full_width() + .child( + div() + .h_px() + .w_full() + .bg(cx.theme().colors().border_variant) + .group_hover("", |style| { + style.bg(cx.theme().colors().border) + }), ) - } else { - this.child(div().size_full().bg(gpui::green())) - } - }) + .when_some(jump_handler, |this, jump_handler| { + this.on_click(jump_handler).tooltip(|cx| { + Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx) + }) + }), + ) }; element.into_any() } From 23fe720ca6bfa21d13cb05ce8f83338a2b93fb3f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 11:24:55 -0700 Subject: [PATCH 134/334] Fix multi-key shortcuts An old fix was ported over from gpui1, and the two fixes could not exist side-by-side. Delete this code and let the keymap handle it --- crates/gpui/src/platform/mac/window.rs | 32 +------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 479e6acab284e8f3fdda45695bd1cc6125ffa031..6d4fd9c4896060b30621804ce532e97e0d369138 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1095,37 +1095,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: .flatten() .is_some(); if !is_composing { - // if the IME has changed the key, we'll first emit an event with the character - // generated by the IME system; then fallback to the keystroke if that is not - // handled. - // cases that we have working: - // - " on a brazillian layout by typing - // - ctrl-` on a brazillian layout by typing - // - $ on a czech QWERTY layout by typing - // - 4 on a czech QWERTY layout by typing - // - ctrl-4 on a czech QWERTY layout by typing (or ) - if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) { - let event_with_ime_text = KeyDownEvent { - is_held: false, - keystroke: Keystroke { - // we match ctrl because some use-cases need it. - // we don't match alt because it's often used to generate the optional character - // we don't match shift because we're not here with letters (usually) - // we don't match cmd/fn because they don't seem to use IME - modifiers: Default::default(), - key: ime_text.clone().unwrap(), - ime_key: None, - }, - }; - handled = callback(InputEvent::KeyDown(event_with_ime_text)); - } - if !handled { - // empty key happens when you type a deadkey in input composition. - // (e.g. on a brazillian keyboard typing quote is a deadkey) - if !event.keystroke.key.is_empty() { - handled = callback(InputEvent::KeyDown(event)); - } - } + handled = callback(InputEvent::KeyDown(event)); } if !handled { From 91d3ba539077d3b03cf07490ff72da20a60c7021 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 11 Jan 2024 10:33:33 -0800 Subject: [PATCH 135/334] Switch to non-destructive migration --- .../20231025085546_move_channel_paths_to_channels_table.sql | 2 -- crates/collab/migrations/20240111085546_fix_column_name.sql | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql create mode 100644 crates/collab/migrations/20240111085546_fix_column_name.sql diff --git a/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql b/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql deleted file mode 100644 index a737c6c273ab494b6e6236feebfab52cf809f4e3..0000000000000000000000000000000000000000 --- a/crates/collab/migrations/20231025085546_move_channel_paths_to_channels_table.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE table_name -RENAME COLUMN enviroment TO environment; \ No newline at end of file diff --git a/crates/collab/migrations/20240111085546_fix_column_name.sql b/crates/collab/migrations/20240111085546_fix_column_name.sql new file mode 100644 index 0000000000000000000000000000000000000000..3f32ee35c59107e12fda98159911dbba6e13434a --- /dev/null +++ b/crates/collab/migrations/20240111085546_fix_column_name.sql @@ -0,0 +1 @@ +ALTER TABLE rooms ADD COLUMN environment TEXT; From d2b15c90e89369145cd4b89acacd911c1a7ad0f1 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 11 Jan 2024 11:00:42 -0800 Subject: [PATCH 136/334] collab 0.36.1 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20394be5bc66dc8c544d4a7394bb8edc7429bd92..58c909081f83d8570440307f3c1e8a300558b72d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.36.0" +version = "0.36.1" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index b0917104f98bc30d193e21171aca81f56199c83b..bc273cb12a9b893ac0e0b7d0b710416ea4ba37b8 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.36.0" +version = "0.36.1" publish = false [[bin]] From 5885f03f35b12c9ffb1a9e799e2023f29bc9f654 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 11 Jan 2024 11:24:17 -0800 Subject: [PATCH 137/334] Add migration information to release docs and fix scripts --- docs/old/release-process.md | 14 +++++++++----- script/deploy-collab | 2 +- script/what-is-deployed | 7 +------ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/old/release-process.md b/docs/old/release-process.md index ce43d647bd723d1343d00d3f1c571c29e2df6092..6162304a7b0a74fe3bfad2cc110d9fbc6a820c51 100644 --- a/docs/old/release-process.md +++ b/docs/old/release-process.md @@ -87,10 +87,14 @@ This means that when releasing a new version of Zed that has changes to the RPC 1. This script will make local changes only, and print out a shell command that you can use to push the branch and tag. 1. Pushing the new tag will trigger a CI build that, when finished will upload a new versioned docker image to the DigitalOcean docker registry. -1. Once that CI job completes, you will be able to run the following command to deploy that docker image. The script takes two arguments: an environment (`production`, `preview`, or `staging`), and a version number (e.g. `0.10.1`). +1. If needing a migration: + - First check that the migration is valid. The database serves both preview and stable simultaneously, so new columns need to have defaults and old tables or columns can't be dropped. + - Then use `script/deploy-migration` (production, staging, preview, nightly). ex: `script/deploy-migration preview 0.19.0` + - If there is an 'Error: container is waiting to start', you can review logs manually with: `kubectl --namespace logs ` to make sure the mgiration ran successfully. +1. Once that CI job completes, you will be able to run the following command to deploy that docker image. The script takes two arguments: an environment (`production`, `preview`, or `staging`), and a version number (e.g. `0.10.1`): - ``` - script/deploy preview 0.10.1 - ``` +``` +script/deploy preview 0.10.1 +``` -1. This command should complete quickly, updating the given environment to use the given version number of the `collab` server. +1. This command should complete quickly, updating the given environment to use the given version number of the `collab` server. \ No newline at end of file diff --git a/script/deploy-collab b/script/deploy-collab index c5386298fa2d5fc8f0357c0d3e96601bf786866e..8dbee18e3b78cfb1a26a74338509a69db54b0307 100755 --- a/script/deploy-collab +++ b/script/deploy-collab @@ -4,7 +4,7 @@ set -eu source script/lib/deploy-helpers.sh if [[ $# < 2 ]]; then - echo "Usage: $0 (nightly is not yet supported)" + echo "Usage: $0 " exit 1 fi environment=$1 diff --git a/script/what-is-deployed b/script/what-is-deployed index b6a68dd3b3245bdf925ffe2d80c23725e43c1c81..c0f9b234878527da913ba320ffcca89d094b2c32 100755 --- a/script/what-is-deployed +++ b/script/what-is-deployed @@ -4,16 +4,11 @@ set -eu source script/lib/deploy-helpers.sh if [[ $# < 1 ]]; then - echo "Usage: $0 (nightly is not yet supported)" + echo "Usage: $0 " exit 1 fi environment=$1 -if [[ ${environment} == "nightly" ]]; then - echo "nightly is not yet supported" - exit 1 -fi - export_vars_for_environment ${environment} target_zed_kube_cluster From a8b8be47e065475c350f539918ac4aff9be9b4e9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 13:52:50 -0700 Subject: [PATCH 138/334] Don't hold platform lock while calling user callbacks Inspired by a bug where using Edit -> Copy from the menu created a deadlock. --- crates/gpui/src/platform/mac/platform.rs | 66 +++++++++++++++++------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 8370e2a4953c1280a59d4a9cb74a93ae97214db2..67af23bf7bd72f886a9ef32b0baca8a7f16bc257 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -985,8 +985,12 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { unsafe { if let Some(event) = InputEvent::from_native(native_event, None) { let platform = get_mac_platform(this); - if let Some(callback) = platform.0.lock().event.as_mut() { - if !callback(event) { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.event.take() { + drop(lock); + let result = callback(event); + platform.0.lock().event.get_or_insert(callback); + if !result { return; } } @@ -1011,30 +1015,42 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { if !has_open_windows { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().reopen.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.reopen.take() { + drop(lock); callback(); + platform.0.lock().reopen.get_or_insert(callback); } } } extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().become_active.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.become_active.take() { + drop(lock); callback(); + platform.0.lock().become_active.get_or_insert(callback); } } extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().resign_active.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.resign_active.take() { + drop(lock); callback(); + platform.0.lock().resign_active.get_or_insert(callback); } } extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().quit.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.quit.take() { + drop(lock); callback(); + platform.0.lock().quit.get_or_insert(callback); } } @@ -1054,22 +1070,27 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { .collect::>() }; let platform = unsafe { get_mac_platform(this) }; - if let Some(callback) = platform.0.lock().open_urls.as_mut() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.open_urls.take() { + drop(lock); callback(urls); + platform.0.lock().open_urls.get_or_insert(callback); } } extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { unsafe { let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.menu_command.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.menu_command.take() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some(action) = platform.menu_actions.get(index) { - callback(action.as_ref()); + if let Some(action) = lock.menu_actions.get(index) { + let action = action.boxed_clone(); + drop(lock); + callback(&*action); } - platform.menu_command = Some(callback); + platform.0.lock().menu_command.get_or_insert(callback); } } } @@ -1078,14 +1099,20 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { unsafe { let mut result = false; let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.validate_menu_command.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.validate_menu_command.take() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some(action) = platform.menu_actions.get(index) { + if let Some(action) = lock.menu_actions.get(index) { + let action = action.boxed_clone(); + drop(lock); result = callback(action.as_ref()); } - platform.validate_menu_command = Some(callback); + platform + .0 + .lock() + .validate_menu_command + .get_or_insert(callback); } result } @@ -1094,10 +1121,11 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { unsafe { let platform = get_mac_platform(this); - let mut platform = platform.0.lock(); - if let Some(mut callback) = platform.will_open_menu.take() { + let mut lock = platform.0.lock(); + if let Some(mut callback) = lock.will_open_menu.take() { + drop(lock); callback(); - platform.will_open_menu = Some(callback); + platform.0.lock().will_open_menu.get_or_insert(callback); } } } From 258c2fdad439f032e57c6643322e27cffce7c224 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 13:41:28 -0800 Subject: [PATCH 139/334] Fix routing of leader updates from unshared projects Previously, leader updates in unshared projects would be sent to all followers regardless of project, as if they were not scoped to any project. --- .../collab/src/tests/channel_buffer_tests.rs | 146 --------------- crates/collab/src/tests/following_tests.rs | 171 +++++++++++++++++- crates/editor/src/items.rs | 4 +- crates/util/src/util.rs | 10 + crates/workspace/src/workspace.rs | 19 +- 5 files changed, 198 insertions(+), 152 deletions(-) diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index 19411ed892b98c613f15d52e19e1365af0738610..76cc8cb9e1793f8b40f184a80b8d9efaaeb6e6d3 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -599,152 +599,6 @@ async fn test_channel_buffers_and_server_restarts( }); } -#[gpui::test(iterations = 10)] -async fn test_following_to_channel_notes_without_a_shared_project( - deterministic: BackgroundExecutor, - mut cx_a: &mut TestAppContext, - mut cx_b: &mut TestAppContext, - mut cx_c: &mut TestAppContext, -) { - let mut server = TestServer::start(deterministic.clone()).await; - let client_a = server.create_client(cx_a, "user_a").await; - let client_b = server.create_client(cx_b, "user_b").await; - - let client_c = server.create_client(cx_c, "user_c").await; - - cx_a.update(editor::init); - cx_b.update(editor::init); - cx_c.update(editor::init); - cx_a.update(collab_ui::channel_view::init); - cx_b.update(collab_ui::channel_view::init); - cx_c.update(collab_ui::channel_view::init); - - let channel_1_id = server - .make_channel( - "channel-1", - None, - (&client_a, cx_a), - &mut [(&client_b, cx_b), (&client_c, cx_c)], - ) - .await; - let channel_2_id = server - .make_channel( - "channel-2", - None, - (&client_a, cx_a), - &mut [(&client_b, cx_b), (&client_c, cx_c)], - ) - .await; - - // Clients A, B, and C join a channel. - let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); - let active_call_c = cx_c.read(ActiveCall::global); - for (call, cx) in [ - (&active_call_a, &mut cx_a), - (&active_call_b, &mut cx_b), - (&active_call_c, &mut cx_c), - ] { - call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx)) - .await - .unwrap(); - } - deterministic.run_until_parked(); - - // Clients A, B, and C all open their own unshared projects. - client_a.fs().insert_tree("/a", json!({})).await; - client_b.fs().insert_tree("/b", json!({})).await; - client_c.fs().insert_tree("/c", json!({})).await; - let (project_a, _) = client_a.build_local_project("/a", cx_a).await; - let (project_b, _) = client_b.build_local_project("/b", cx_b).await; - let (project_c, _) = client_b.build_local_project("/c", cx_c).await; - let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); - let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c); - - active_call_a - .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) - .await - .unwrap(); - - // Client A opens the notes for channel 1. - let channel_view_1_a = cx_a - .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx)) - .await - .unwrap(); - channel_view_1_a.update(cx_a, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); - notes.editor.update(cx, |editor, cx| { - editor.insert("Hello from A.", cx); - editor.change_selections(None, cx, |selections| { - selections.select_ranges(vec![3..4]); - }); - }); - }); - - // Client B follows client A. - workspace_b - .update(cx_b, |workspace, cx| { - workspace - .start_following(client_a.peer_id().unwrap(), cx) - .unwrap() - }) - .await - .unwrap(); - - // Client B is taken to the notes for channel 1, with the same - // text selected as client A. - deterministic.run_until_parked(); - let channel_view_1_b = workspace_b.update(cx_b, |workspace, cx| { - assert_eq!( - workspace.leader_for_pane(workspace.active_pane()), - Some(client_a.peer_id().unwrap()) - ); - workspace - .active_item(cx) - .expect("no active item") - .downcast::() - .expect("active item is not a channel view") - }); - channel_view_1_b.update(cx_b, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); - let editor = notes.editor.read(cx); - assert_eq!(editor.text(cx), "Hello from A."); - assert_eq!(editor.selections.ranges::(cx), &[3..4]); - }); - - // Client A opens the notes for channel 2. - eprintln!("opening -------------------->"); - - let channel_view_2_a = cx_a - .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx)) - .await - .unwrap(); - channel_view_2_a.update(cx_a, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); - }); - - // Client B is taken to the notes for channel 2. - deterministic.run_until_parked(); - - eprintln!("opening <--------------------"); - - let channel_view_2_b = workspace_b.update(cx_b, |workspace, cx| { - assert_eq!( - workspace.leader_for_pane(workspace.active_pane()), - Some(client_a.peer_id().unwrap()) - ); - workspace - .active_item(cx) - .expect("no active item") - .downcast::() - .expect("active item is not a channel view") - }); - channel_view_2_b.update(cx_b, |notes, cx| { - assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); - }); -} - #[gpui::test] async fn test_channel_buffer_changes( deterministic: BackgroundExecutor, diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 6106f8d5f1531302374de4456bf9fd9aff11b871..dc5488ebb335ba0e1ecce9800a7c689d217d5a0e 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1,9 +1,12 @@ use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; use call::{ActiveCall, ParticipantLocation}; -use collab_ui::notifications::project_shared_notification::ProjectSharedNotification; +use collab_ui::{ + channel_view::ChannelView, + notifications::project_shared_notification::ProjectSharedNotification, +}; use editor::{Editor, ExcerptRange, MultiBuffer}; use gpui::{ - point, BackgroundExecutor, Context, SharedString, TestAppContext, View, VisualContext, + point, BackgroundExecutor, Context, Entity, SharedString, TestAppContext, View, VisualContext, VisualTestContext, }; use language::Capability; @@ -1822,3 +1825,167 @@ fn pane_summaries(workspace: &View, cx: &mut VisualTestContext) -> Ve .collect() }) } + +#[gpui::test(iterations = 10)] +async fn test_following_to_channel_notes_without_a_shared_project( + deterministic: BackgroundExecutor, + mut cx_a: &mut TestAppContext, + mut cx_b: &mut TestAppContext, + mut cx_c: &mut TestAppContext, +) { + let mut server = TestServer::start(deterministic.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + + cx_a.update(editor::init); + cx_b.update(editor::init); + cx_c.update(editor::init); + cx_a.update(collab_ui::channel_view::init); + cx_b.update(collab_ui::channel_view::init); + cx_c.update(collab_ui::channel_view::init); + + let channel_1_id = server + .make_channel( + "channel-1", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b), (&client_c, cx_c)], + ) + .await; + let channel_2_id = server + .make_channel( + "channel-2", + None, + (&client_a, cx_a), + &mut [(&client_b, cx_b), (&client_c, cx_c)], + ) + .await; + + // Clients A, B, and C join a channel. + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + let active_call_c = cx_c.read(ActiveCall::global); + for (call, cx) in [ + (&active_call_a, &mut cx_a), + (&active_call_b, &mut cx_b), + (&active_call_c, &mut cx_c), + ] { + call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx)) + .await + .unwrap(); + } + deterministic.run_until_parked(); + + // Clients A, B, and C all open their own unshared projects. + client_a + .fs() + .insert_tree("/a", json!({ "1.txt": "" })) + .await; + client_b.fs().insert_tree("/b", json!({})).await; + client_c.fs().insert_tree("/c", json!({})).await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_b, _) = client_b.build_local_project("/b", cx_b).await; + let (project_c, _) = client_b.build_local_project("/c", cx_c).await; + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c); + + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) + .await + .unwrap(); + + // Client A opens the notes for channel 1. + let channel_notes_1_a = cx_a + .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx)) + .await + .unwrap(); + channel_notes_1_a.update(cx_a, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); + notes.editor.update(cx, |editor, cx| { + editor.insert("Hello from A.", cx); + editor.change_selections(None, cx, |selections| { + selections.select_ranges(vec![3..4]); + }); + }); + }); + + // Client B follows client A. + workspace_b + .update(cx_b, |workspace, cx| { + workspace + .start_following(client_a.peer_id().unwrap(), cx) + .unwrap() + }) + .await + .unwrap(); + + // Client B is taken to the notes for channel 1, with the same + // text selected as client A. + deterministic.run_until_parked(); + let channel_notes_1_b = workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.leader_for_pane(workspace.active_pane()), + Some(client_a.peer_id().unwrap()) + ); + workspace + .active_item(cx) + .expect("no active item") + .downcast::() + .expect("active item is not a channel view") + }); + channel_notes_1_b.update(cx_b, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); + let editor = notes.editor.read(cx); + assert_eq!(editor.text(cx), "Hello from A."); + assert_eq!(editor.selections.ranges::(cx), &[3..4]); + }); + + // Client A opens the notes for channel 2. + let channel_notes_2_a = cx_a + .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx)) + .await + .unwrap(); + channel_notes_2_a.update(cx_a, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); + }); + + // Client B is taken to the notes for channel 2. + deterministic.run_until_parked(); + let channel_notes_2_b = workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.leader_for_pane(workspace.active_pane()), + Some(client_a.peer_id().unwrap()) + ); + workspace + .active_item(cx) + .expect("no active item") + .downcast::() + .expect("active item is not a channel view") + }); + channel_notes_2_b.update(cx_b, |notes, cx| { + assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); + }); + + // Client A opens a local buffer in their unshared project. + let _unshared_editor_a1 = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path((worktree_id, "1.txt"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + // This does not send any leader update message to client B. + // If it did, an error would occur on client B, since this buffer + // is not shared with them. + deterministic.run_until_parked(); + workspace_b.update(cx_b, |workspace, cx| { + assert_eq!( + workspace.active_item(cx).expect("no active item").item_id(), + channel_notes_2_b.entity_id() + ); + }); +} diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 78f9b150512e6ee153b3165e88bc162ea015aa14..a8583d48afae6abfee508026a65d109f78aafc95 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -82,7 +82,9 @@ impl FollowableItem for Editor { let pane = pane.downgrade(); Some(cx.spawn(|mut cx| async move { - let mut buffers = futures::future::try_join_all(buffers).await?; + let mut buffers = futures::future::try_join_all(buffers) + .await + .debug_assert_ok("leaders don't share views for unshared buffers")?; let editor = pane.update(&mut cx, |pane, cx| { let mut editors = pane.items_of_type::(); editors.find(|editor| { diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index e32dd88b86fab6f0e8ad3fa9de1ce7ce6e8793a6..a4031da8cde538eac1833a85d08068488086b9a7 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -137,6 +137,8 @@ pub trait ResultExt { type Ok; fn log_err(self) -> Option; + /// Assert that this result should never be an error in development or tests. + fn debug_assert_ok(self, reason: &str) -> Self; fn warn_on_err(self) -> Option; fn inspect_error(self, func: impl FnOnce(&E)) -> Self; } @@ -159,6 +161,14 @@ where } } + #[track_caller] + fn debug_assert_ok(self, reason: &str) -> Self { + if let Err(error) = &self { + debug_panic!("{reason} - {error:?}"); + } + self + } + fn warn_on_err(self) -> Option { match self { Ok(value) => Some(value), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index db8554712e68b091fa59d20f71c61e1824a5cf05..ca463e76e058c645839f853eadd8e0877ecca6da 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2608,11 +2608,20 @@ impl Workspace { let cx = &cx; move |item| { let item = item.to_followable_item_handle(cx)?; - if (project_id.is_none() || project_id != follower_project_id) - && item.is_project_item(cx) + + // If the item belongs to a particular project, then it should + // only be included if this project is shared, and the follower + // is in thie project. + // + // Some items, like channel notes, do not belong to a particular + // project, so they should be included regardless of whether the + // current project is shared, or what project the follower is in. + if item.is_project_item(cx) + && (project_id.is_none() || project_id != follower_project_id) { return None; } + let id = item.remote_id(client, cx)?.to_proto(); let variant = item.to_state_proto(cx)?; Some(proto::View { @@ -2790,8 +2799,12 @@ impl Workspace { update: proto::update_followers::Variant, cx: &mut WindowContext, ) -> Option<()> { + // If this update only applies to for followers in the current project, + // then skip it unless this project is shared. If it applies to all + // followers, regardless of project, then set `project_id` to none, + // indicating that it goes to all followers. let project_id = if project_only { - self.project.read(cx).remote_id() + Some(self.project.read(cx).remote_id()?) } else { None }; From a1049546a2577b8458e64d526dbb3ad866448e6a Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:58:36 +0100 Subject: [PATCH 140/334] gpui: Validate font contents at load time. During layout of EditorElement we use 'm' character from current font to calculate sizes, panicking with fonts that do not have that character (e.g. Arabic fonts). It's not really EditorElement's fault, as it assumes that the font it's dealing with is gonna have that character available. To prevent a crash, I added validation while loading a family that a given font contains the glyphs we're gonna use down the line. --- crates/gpui/src/platform/mac/text_system.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 79ffb8dc8e4aa54b954494a8cae4f3ca6186b377..68f4a63326757f8a87217a133aef005d0c8e5c86 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -190,6 +190,9 @@ impl MacTextSystemState { for font in family.fonts() { let mut font = font.load()?; open_type::apply_features(&mut font, features); + let Some(_) = font.glyph_for_char('m') else { + continue; + }; let font_id = FontId(self.fonts.len()); font_ids.push(font_id); let postscript_name = font.postscript_name().unwrap(); From 8d294211db81ce1d70a2e0de80da7995700ccfd3 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 11 Jan 2024 23:05:27 +0100 Subject: [PATCH 141/334] settings.json: Suggest font names for buffer_font_family --- crates/gpui/src/text_system.rs | 3 +++ crates/theme/src/settings.rs | 32 +++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 2d3cc34f3fc09e6402ece055f9aa6ba45fe5434e..d80c9163a996be96580f459c13e0e8eba8e71d10 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -65,6 +65,9 @@ impl TextSystem { } } + pub fn all_font_families(&self) -> Vec { + self.platform_text_system.all_font_families() + } pub fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { self.platform_text_system.add_fonts(fonts) } diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 3ecf1935a47eeb7fb4c8e4edbce9d99f33a2b3f3..e51ff81b012aceec95aa296e4b8d1a2b58642288 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -194,9 +194,21 @@ impl settings::Settings for ThemeSettings { ..Default::default() }; - root_schema - .definitions - .extend([("ThemeName".into(), theme_name_schema.into())]); + let available_fonts = cx + .text_system() + .all_font_families() + .into_iter() + .map(Value::String) + .collect(); + let fonts_schema = SchemaObject { + instance_type: Some(InstanceType::String.into()), + enum_values: Some(available_fonts), + ..Default::default() + }; + root_schema.definitions.extend([ + ("ThemeName".into(), theme_name_schema.into()), + ("FontFamilies".into(), fonts_schema.into()), + ]); root_schema .schema @@ -204,10 +216,16 @@ impl settings::Settings for ThemeSettings { .as_mut() .unwrap() .properties - .extend([( - "theme".to_owned(), - Schema::new_ref("#/definitions/ThemeName".into()), - )]); + .extend([ + ( + "theme".to_owned(), + Schema::new_ref("#/definitions/ThemeName".into()), + ), + ( + "buffer_font_family".to_owned(), + Schema::new_ref("#/definitions/FontFamilies".into()), + ), + ]); root_schema } From 9d50697caabc351255a15443c7d57cbbc846832d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 15:44:43 -0800 Subject: [PATCH 142/334] Temporarily avoid releasing livekit RemoteAudioTracks on drop This release call was added during the conversion to gpui2. I think it is probably valid, but want to remove it on the off chance that it is causing the crash that we're seeing in the `livekit.multicast` thread when leaving a room. --- crates/live_kit_client/src/prod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 0827c0cbb41092f7643891c71c8b8c1f279a0860..3082c9b5338d7449b00a86fe60287213acd1e79d 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -864,7 +864,10 @@ impl RemoteAudioTrack { impl Drop for RemoteAudioTrack { fn drop(&mut self) { - unsafe { CFRelease(self.native_track.0) } + // todo: uncomment this `CFRelease`, unless we find that it was causing + // the crash in the `livekit.multicast` thread. + // + // unsafe { CFRelease(self.native_track.0) } } } From 02029c945a0f9c7bbb2c680102afd5c2d66f93c3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:21:55 -0800 Subject: [PATCH 143/334] Suppress unused field warning --- crates/live_kit_client/src/prod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index 3082c9b5338d7449b00a86fe60287213acd1e79d..f1660cc3d1a7ce9f7951cfd5f1a354158a41d334 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -868,6 +868,7 @@ impl Drop for RemoteAudioTrack { // the crash in the `livekit.multicast` thread. // // unsafe { CFRelease(self.native_track.0) } + let _ = self.native_track; } } From 08a4307d716046ac77233d82fbe90b77383024c9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:22:34 -0800 Subject: [PATCH 144/334] Fix failure to write to keychain because of dropping a future --- crates/client/src/client.rs | 7 ++----- crates/util/src/util.rs | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 0821a8e534d9713b312667dce33eb2c11c93d429..189402308487870bee29da52c9333283ceb7fd1f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1371,10 +1371,7 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { }) } -async fn write_credentials_to_keychain( - credentials: Credentials, - cx: &AsyncAppContext, -) -> Result<()> { +fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext) -> Result<()> { cx.update(move |cx| { cx.write_credentials( &ZED_SERVER_URL, @@ -1384,7 +1381,7 @@ async fn write_credentials_to_keychain( })? } -async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { +fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index a4031da8cde538eac1833a85d08068488086b9a7..a2f8b87feea91e5ccb09aea4c752cd1c5a583fda 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -244,6 +244,7 @@ where } } +#[must_use] pub struct LogErrorFuture(F, log::Level, core::panic::Location<'static>); impl Future for LogErrorFuture From 5f5505fe9a533d0e00e770a4390de1f4cf008726 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Jan 2024 16:51:40 -0800 Subject: [PATCH 145/334] Don't pass zed-local flags through to zed --- script/zed-local | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/script/zed-local b/script/zed-local index 4519ede38cf06bab355d0e65c530b76426e9108c..090fbd58760cd04779340be19d06a199d2a0a01d 100755 --- a/script/zed-local +++ b/script/zed-local @@ -1,31 +1,44 @@ #!/usr/bin/env node +const HELP = ` +USAGE + zed-local [options] [zed args] + +OPTIONS + --help Print this help message + --release Build Zed in release mode + -2, -3, -4 Spawn 2, 3, or 4 Zed instances, with their windows tiled. + --top Arrange the Zed windows so they take up the top half of the screen. +`.trim(); + const { spawn, execFileSync } = require("child_process"); const RESOLUTION_REGEX = /(\d+) x (\d+)/; const DIGIT_FLAG_REGEX = /^--?(\d+)$/; -// Parse the number of Zed instances to spawn. let instanceCount = 1; let isReleaseMode = false; let isTop = false; const args = process.argv.slice(2); -for (const arg of args) { +while (args.length > 0) { + const arg = args[0]; + const digitMatch = arg.match(DIGIT_FLAG_REGEX); if (digitMatch) { instanceCount = parseInt(digitMatch[1]); - continue; - } - - if (arg == "--release") { + } else if (arg === "--release") { isReleaseMode = true; - continue; - } - - if (arg == "--top") { + } else if (arg === "--top") { isTop = true; + } else if (arg === "--help") { + console.log(HELP); + process.exit(0); + } else { + break; } + + args.shift(); } // Parse the resolution of the main screen From d20ed0aacfb7ca13eef23f21ca4a94128791ea1e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 20:21:41 -0700 Subject: [PATCH 146/334] Try sqwauk --- .github/actions/check_style/action.yml | 34 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 5dc7c42b02f387fe76295e9ae110f28972ef8450..e6eed28b66b9fe73ceb8c1c40fb2f5a6a298ec15 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -2,16 +2,26 @@ name: "Check formatting" description: "Checks code formatting use cargo fmt" runs: - using: "composite" - steps: - - name: cargo fmt - shell: bash -euxo pipefail {0} - run: cargo fmt --all -- --check + using: "composite" + steps: + - name: cargo fmt + shell: bash -euxo pipefail {0} + run: cargo fmt --all -- --check - - name: cargo clippy - shell: bash -euxo pipefail {0} - # clippy.toml is not currently supporting specifying allowed lints - # so specify those here, and disable the rest until Zed's workspace - # will have more fixes & suppression for the standard lint set - run: | - cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + - name: cargo clippy + shell: bash -euxo pipefail {0} + # clippy.toml is not currently supporting specifying allowed lints + # so specify those here, and disable the rest until Zed's workspace + # will have more fixes & suppression for the standard lint set + run: | + cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo + + - name: Find modified migrations + shell: bash -euxo pipefail {0} + run: | + modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crate/collab/migrations/*.sql') + echo "::set-output name=file_names::$modified_migrations" + id: modified-migrations + - uses: sbdchd/squawk-action@v1 + with: + pattern: ${{ steps.modified-migrations.outputs.file_names }} From 403fa7fbc90572653ccc6a3cabc57c708e986f4b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 20:40:34 -0700 Subject: [PATCH 147/334] Try the manual approach... --- .github/actions/check_style/action.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index e6eed28b66b9fe73ceb8c1c40fb2f5a6a298ec15..3174376736c79817fe077702a1483a61a283d04e 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -19,9 +19,11 @@ runs: - name: Find modified migrations shell: bash -euxo pipefail {0} run: | + cargo install squawk --git https://github.com/sbdchd/squawk --tag v0.26.0 modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crate/collab/migrations/*.sql') - echo "::set-output name=file_names::$modified_migrations" - id: modified-migrations - - uses: sbdchd/squawk-action@v1 - with: - pattern: ${{ steps.modified-migrations.outputs.file_names }} + + SQUAWK_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}') + SQUAWK_GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $2}') + SQUAWK_GITHUB_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') + squawk upload-to-github $(modified_migrations) From 51e4db7d702fee06724bc1e4644871ee5ed6c959 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 20:43:49 -0700 Subject: [PATCH 148/334] try again --- .github/actions/check_style/action.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 3174376736c79817fe077702a1483a61a283d04e..d2f648ea8fa1e0cc7d2a1d9b0eb508d7088b3455 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -21,8 +21,7 @@ runs: run: | cargo install squawk --git https://github.com/sbdchd/squawk --tag v0.26.0 modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crate/collab/migrations/*.sql') - - SQUAWK_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + SQUAWK_GITHUB_TOKEN=${{ github.token }} SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}') SQUAWK_GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $2}') SQUAWK_GITHUB_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') From 87ccbf6c19223da70318dd3ff5ecea5ad533db51 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 11 Jan 2024 21:09:50 -0700 Subject: [PATCH 149/334] One of these days... --- .github/actions/check_style/action.yml | 9 ++------- script/squawk | 27 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) create mode 100755 script/squawk diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index d2f648ea8fa1e0cc7d2a1d9b0eb508d7088b3455..25020e4e4c5399026c1ab32622903a3779ba86b2 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -19,10 +19,5 @@ runs: - name: Find modified migrations shell: bash -euxo pipefail {0} run: | - cargo install squawk --git https://github.com/sbdchd/squawk --tag v0.26.0 - modified_migrations=$(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crate/collab/migrations/*.sql') - SQUAWK_GITHUB_TOKEN=${{ github.token }} - SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}') - SQUAWK_GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $2}') - SQUAWK_GITHUB_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') - squawk upload-to-github $(modified_migrations) + export SQUAWK_GITHUB_TOKEN=${{ github.token }} + . ./script/squawk diff --git a/script/squawk b/script/squawk new file mode 100755 index 0000000000000000000000000000000000000000..e4ade6fbed6e77dd7fc791ed33988c02020f0078 --- /dev/null +++ b/script/squawk @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# Squawk is a linter for database migrations. It helps identify dangerous patterns, and suggests alternatives. +# Squawk flagging an error does not mean that you need to take a different approach, but it does indicate you need to think about what you're doing. +# See also: https://squawkhq.com + +set -e + +SQUAWK_VERSION=0.26.0 +SQUAWK_BIN="./target/squawk-$SQUAWK_VERSION" +SQUAWK_ARGS="--assume-in-transaction" + + +if [ ! -f "$SQUAWK_BIN" ]; then + curl -L -o "$SQUAWK_BIN" "https://github.com/sbdchd/squawk/releases/download/v$SQUAWK_VERSION/squawk-darwin-x86_64" + chmod +x "$SQUAWK_BIN" +fi + +if [ -n "$SQUAWK_GITHUB_TOKEN" ]; then + export SQUAWK_GITHUB_REPO_OWNER=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $1}') + export SQUAWK_GITHUB_REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F/ '{print $2}') + export SQUAWK_GITHUB_PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') + + $SQUAWK_BIN $SQUAWK_ARGS upload-to-github $(git diff --name-only origin/$GITHUB_BASE_REF...origin/$GITHUB_HEAD_REF 'crates/collab/migrations/*.sql') +else + $SQUAWK_BIN $SQUAWK_ARGS $(git ls-files --others crates/collab/migrations/*.sql) $(git diff --name-only main crates/collab/migrations/*.sql) +fi From a81f48f36b4ba5d73e481e85b2981651df7388ac Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 12 Jan 2024 12:00:18 +0200 Subject: [PATCH 150/334] Use auto formatter settings for Zed repo. Some users might have a language server formatter set in their settings, which is exclusive with prettier formatting. That causes disruptions in the way whitespaces and other things are formatted, esp. in json and yaml files, hence enforce one formatter settings for the entire Zed repo. --- .zed/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.zed/settings.json b/.zed/settings.json index d4b3375b0d21f95128c6bffb2c7a92f8bf97916c..205d610046fef48bbfa03911d2ababe86755dc71 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,5 +1,6 @@ { "JSON": { "tab_size": 4 - } + }, + "formatter": "auto" } From a32ad3f9074026f1a7bf13e96b8534095e4bfc31 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Jan 2024 14:50:42 +0100 Subject: [PATCH 151/334] Fix editor tests --- crates/editor/src/element.rs | 58 ++++++++++++++++++++---------------- crates/gpui/src/window.rs | 12 ++++---- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 29d3e0aaea38011a120401eb54aabb1a311bd3b6..fcf2ff36108b319b7b18990e0c9280ad8b752f6e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3404,14 +3404,16 @@ mod tests { }) .unwrap(); let state = cx - .update_window(window.into(), |_, cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + .update_window(window.into(), |view, cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) .unwrap(); @@ -3496,14 +3498,16 @@ mod tests { }); let state = cx - .update_window(window.into(), |_, cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + .update_window(window.into(), |view, cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) .unwrap(); assert_eq!(state.selections.len(), 1); @@ -3558,14 +3562,16 @@ mod tests { let mut element = EditorElement::new(&editor, style); let state = cx - .update_window(window.into(), |_, cx| { - element.compute_layout( - Bounds { - origin: point(px(500.), px(500.)), - size: size(px(500.), px(500.)), - }, - cx, - ) + .update_window(window.into(), |view, cx| { + cx.with_view_id(view.entity_id(), |cx| { + element.compute_layout( + Bounds { + origin: point(px(500.), px(500.)), + size: size(px(500.), px(500.)), + }, + cx, + ) + }) }) .unwrap(); let size = state.position_map.size; @@ -3582,8 +3588,8 @@ mod tests { // Don't panic. let bounds = Bounds::::new(Default::default(), size); - cx.update_window(window.into(), |_, cx| { - element.paint(bounds, &mut (), cx); + cx.update_window(window.into(), |view, cx| { + cx.paint_view(view.entity_id(), |cx| element.paint(bounds, &mut (), cx)) }) .unwrap() } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index f34342899660ac65b0206cbf582b5790e761f9be..bc5e7ee765941cca2af6b875ffaac1fbc68cf58a 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2004,11 +2004,9 @@ impl<'a> WindowContext<'a> { result } - pub(crate) fn with_view_id( - &mut self, - view_id: EntityId, - f: impl FnOnce(&mut Self) -> R, - ) -> R { + /// Invoke the given function with the given view id present on the view stack. + /// This is a fairly low-level method used to layout views. + pub fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { let text_system = self.text_system().clone(); text_system.with_view(view_id, || { self.window.next_frame.view_stack.push(view_id); @@ -2018,7 +2016,9 @@ impl<'a> WindowContext<'a> { }) } - pub(crate) fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { + /// Invoke the given function with the given view id present on the view stack. + /// This is a fairly low-level method used to paint views. + pub fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { self.with_view_id(view_id, |cx| { cx.window .next_frame From bc09ce6ffcdf9de313209107c836c2b1792749c7 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 12 Jan 2024 10:37:18 -0500 Subject: [PATCH 152/334] Clean up some theme TODOs (#4034) This PR cleans up some TODOs we had in the themes. We won't be addressing these in the immediate term, so no need to have them show up when folks are looking for TODOs to burn down before launch. I added some general notes as signposts until we're ready to revisit this. Release Notes: - N/A --- crates/theme/src/default_colors.rs | 29 +++++++++++++++++------------ crates/theme/src/one_themes.rs | 6 +++++- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 8cbfc23fa34655283ddaa5418e15922929c1b5bc..fb88afb7dacf79d947a170e26136b293d093542b 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -8,6 +8,11 @@ pub(crate) fn neutral() -> ColorScaleSet { sand() } +// Note: We aren't currently making use of the default colors, as all of the +// themes have a value set for each color. +// +// We'll need to revisit these once we're ready to launch user themes, which may +// not specify a value for each color (and thus should fall back to the defaults). impl ThemeColors { pub fn light() -> Self { let system = SystemColors::default(); @@ -23,12 +28,12 @@ impl ThemeColors { surface_background: neutral().light().step_2(), background: neutral().light().step_1(), element_background: neutral().light().step_3(), - element_hover: neutral().light_alpha().step_4(), // todo!("pick the right colors") + element_hover: neutral().light_alpha().step_4(), element_active: neutral().light_alpha().step_5(), element_selected: neutral().light_alpha().step_5(), - element_disabled: neutral().light_alpha().step_3(), // todo!("pick the right colors") - drop_target_background: blue().light_alpha().step_2(), // todo!("pick the right colors") - ghost_element_background: system.transparent, // todo!("pick the right colors") + element_disabled: neutral().light_alpha().step_3(), + drop_target_background: blue().light_alpha().step_2(), + ghost_element_background: system.transparent, ghost_element_hover: neutral().light_alpha().step_3(), ghost_element_active: neutral().light_alpha().step_4(), ghost_element_selected: neutral().light_alpha().step_5(), @@ -59,7 +64,7 @@ impl ThemeColors { scrollbar_track_background: gpui::transparent_black(), scrollbar_track_border: neutral().light().step_5(), editor_foreground: neutral().light().step_12(), - editor_background: neutral().light().step_1(), // todo!(this was inserted by Mikayla) + editor_background: neutral().light().step_1(), editor_gutter_background: neutral().light().step_1(), editor_subheader_background: neutral().light().step_2(), editor_active_line_background: neutral().light_alpha().step_3(), @@ -106,17 +111,17 @@ impl ThemeColors { surface_background: neutral().dark().step_2(), background: neutral().dark().step_1(), element_background: neutral().dark().step_3(), - element_hover: neutral().dark_alpha().step_4(), // todo!("pick the right colors") + element_hover: neutral().dark_alpha().step_4(), element_active: neutral().dark_alpha().step_5(), - element_selected: neutral().dark_alpha().step_5(), // todo!("pick the right colors") - element_disabled: neutral().dark_alpha().step_3(), // todo!("pick the right colors") + element_selected: neutral().dark_alpha().step_5(), + element_disabled: neutral().dark_alpha().step_3(), drop_target_background: blue().dark_alpha().step_2(), ghost_element_background: system.transparent, - ghost_element_hover: neutral().dark_alpha().step_4(), // todo!("pick the right colors") - ghost_element_active: neutral().dark_alpha().step_5(), // todo!("pick the right colors") + ghost_element_hover: neutral().dark_alpha().step_4(), + ghost_element_active: neutral().dark_alpha().step_5(), ghost_element_selected: neutral().dark_alpha().step_5(), ghost_element_disabled: neutral().dark_alpha().step_3(), - text: neutral().dark().step_12(), // todo!("pick the right colors") + text: neutral().dark().step_12(), text_muted: neutral().dark().step_11(), text_placeholder: neutral().dark().step_10(), text_disabled: neutral().dark().step_9(), @@ -140,7 +145,7 @@ impl ThemeColors { scrollbar_thumb_hover_background: neutral().dark_alpha().step_4(), scrollbar_thumb_border: gpui::transparent_black(), scrollbar_track_background: gpui::transparent_black(), - scrollbar_track_border: neutral().dark().step_5(), // todo!(this was inserted by Mikayla) + scrollbar_track_border: neutral().dark().step_5(), editor_foreground: neutral().dark().step_12(), editor_background: neutral().dark().step_1(), editor_gutter_background: neutral().dark().step_1(), diff --git a/crates/theme/src/one_themes.rs b/crates/theme/src/one_themes.rs index d0bae590f6d32797ec65b34eb39de13d4befe177..fab3631d13ca890ca3ccb9fa2e06605534602abd 100644 --- a/crates/theme/src/one_themes.rs +++ b/crates/theme/src/one_themes.rs @@ -7,6 +7,10 @@ use crate::{ ThemeColors, ThemeFamily, ThemeStyles, }; +// Note: This theme family is not the one you see in Zed at the moment. +// This is a from-scratch rebuild that Nate started work on. We currently +// only use this in the tests, and the One family from the `themes/` directory +// is what gets loaded into Zed when running it. pub fn one_family() -> ThemeFamily { ThemeFamily { id: "one".to_string(), @@ -75,7 +79,7 @@ pub(crate) fn one_dark() -> Theme { tab_bar_background: bg, tab_inactive_background: bg, tab_active_background: editor, - search_match_background: bg, // todo!(this was inserted by Mikayla) + search_match_background: bg, editor_background: editor, editor_gutter_background: editor, From 817b641c17d2ab5aed2debed61e1dda269690540 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Jan 2024 17:36:11 +0100 Subject: [PATCH 153/334] Ensure editor elements invalidate their parent views on notify Co-Authored-By: Nathan Sobo Co-Authored-By: Conrad Irwin --- crates/editor/src/element.rs | 201 ++++++++++++++++++----------------- crates/gpui/src/window.rs | 34 +++--- 2 files changed, 125 insertions(+), 110 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fcf2ff36108b319b7b18990e0c9280ad8b752f6e..a2f1b0c09dd1f48bef0d8111054756d48e433589 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -26,11 +26,11 @@ use git::diff::DiffHunkStatus; use gpui::{ div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, - CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Hsla, InteractiveBounds, - InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, - SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, - TextStyle, View, ViewContext, WindowContext, + CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, + InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, + ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement, + Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, }; use itertools::Itertools; use language::language_settings::ShowWhitespaceSetting; @@ -2801,44 +2801,49 @@ impl Element for EditorElement { _element_state: Option, cx: &mut gpui::WindowContext, ) -> (gpui::LayoutId, Self::State) { - self.editor.update(cx, |editor, cx| { - editor.set_style(self.style.clone(), cx); - - let layout_id = match editor.mode { - EditorMode::SingleLine => { - let rem_size = cx.rem_size(); - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); - cx.request_layout(&style, None) - } - EditorMode::AutoHeight { max_lines } => { - let editor_handle = cx.view().clone(); - let max_line_number_width = - self.max_line_number_width(&editor.snapshot(cx), cx); - cx.request_measured_layout(Style::default(), move |known_dimensions, _, cx| { - editor_handle - .update(cx, |editor, cx| { - compute_auto_height_layout( - editor, - max_lines, - max_line_number_width, - known_dimensions, - cx, - ) - }) - .unwrap_or_default() - }) - } - EditorMode::Full => { - let mut style = Style::default(); - style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); - cx.request_layout(&style, None) - } - }; + cx.with_view_id(self.editor.entity_id(), |cx| { + self.editor.update(cx, |editor, cx| { + editor.set_style(self.style.clone(), cx); + + let layout_id = match editor.mode { + EditorMode::SingleLine => { + let rem_size = cx.rem_size(); + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = self.style.text.line_height_in_pixels(rem_size).into(); + cx.request_layout(&style, None) + } + EditorMode::AutoHeight { max_lines } => { + let editor_handle = cx.view().clone(); + let max_line_number_width = + self.max_line_number_width(&editor.snapshot(cx), cx); + cx.request_measured_layout( + Style::default(), + move |known_dimensions, _, cx| { + editor_handle + .update(cx, |editor, cx| { + compute_auto_height_layout( + editor, + max_lines, + max_line_number_width, + known_dimensions, + cx, + ) + }) + .unwrap_or_default() + }, + ) + } + EditorMode::Full => { + let mut style = Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + cx.request_layout(&style, None) + } + }; - (layout_id, ()) + (layout_id, ()) + }) }) } @@ -2850,65 +2855,67 @@ impl Element for EditorElement { ) { let editor = self.editor.clone(); - cx.with_text_style( - Some(gpui::TextStyleRefinement { - font_size: Some(self.style.text.font_size), - ..Default::default() - }), - |cx| { - let mut layout = self.compute_layout(bounds, cx); - let gutter_bounds = Bounds { - origin: bounds.origin, - size: layout.gutter_size, - }; - let text_bounds = Bounds { - origin: gutter_bounds.upper_right(), - size: layout.text_size, - }; + cx.paint_view(self.editor.entity_id(), |cx| { + cx.with_text_style( + Some(gpui::TextStyleRefinement { + font_size: Some(self.style.text.font_size), + ..Default::default() + }), + |cx| { + let mut layout = self.compute_layout(bounds, cx); + let gutter_bounds = Bounds { + origin: bounds.origin, + size: layout.gutter_size, + }; + let text_bounds = Bounds { + origin: gutter_bounds.upper_right(), + size: layout.text_size, + }; - let focus_handle = editor.focus_handle(cx); - let key_context = self.editor.read(cx).key_context(cx); - cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| { - self.register_actions(cx); - self.register_key_listeners(cx); + let focus_handle = editor.focus_handle(cx); + let key_context = self.editor.read(cx).key_context(cx); + cx.with_key_dispatch(Some(key_context), Some(focus_handle.clone()), |_, cx| { + self.register_actions(cx); + self.register_key_listeners(cx); - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - let input_handler = - ElementInputHandler::new(bounds, self.editor.clone(), cx); - cx.handle_input(&focus_handle, input_handler); + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + let input_handler = + ElementInputHandler::new(bounds, self.editor.clone(), cx); + cx.handle_input(&focus_handle, input_handler); - self.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &mut layout, cx); - } - self.paint_text(text_bounds, &mut layout, cx); + self.paint_background(gutter_bounds, text_bounds, &layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, &mut layout, cx); + } + self.paint_text(text_bounds, &mut layout, cx); - cx.with_z_index(0, |cx| { - self.paint_mouse_listeners( - bounds, - gutter_bounds, - text_bounds, - &layout, - cx, - ); - }); - if !layout.blocks.is_empty() { cx.with_z_index(0, |cx| { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, cx); - }); - }) - } + self.paint_mouse_listeners( + bounds, + gutter_bounds, + text_bounds, + &layout, + cx, + ); + }); + if !layout.blocks.is_empty() { + cx.with_z_index(0, |cx| { + cx.with_element_id(Some("editor_blocks"), |cx| { + self.paint_blocks(bounds, &mut layout, cx); + }); + }) + } - cx.with_z_index(1, |cx| { - self.paint_overlays(text_bounds, &mut layout, cx); - }); + cx.with_z_index(1, |cx| { + self.paint_overlays(text_bounds, &mut layout, cx); + }); - cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); - }); - }) - }, - ); + cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); + }); + }) + }, + ) + }) } } @@ -3588,10 +3595,8 @@ mod tests { // Don't panic. let bounds = Bounds::::new(Default::default(), size); - cx.update_window(window.into(), |view, cx| { - cx.paint_view(view.entity_id(), |cx| element.paint(bounds, &mut (), cx)) - }) - .unwrap() + cx.update_window(window.into(), |view, cx| element.paint(bounds, &mut (), cx)) + .unwrap() } #[gpui::test] diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index bc5e7ee765941cca2af6b875ffaac1fbc68cf58a..619a1bfeb6c23071a1c7e8071b396eb908bcf0f8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2009,24 +2009,34 @@ impl<'a> WindowContext<'a> { pub fn with_view_id(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { let text_system = self.text_system().clone(); text_system.with_view(view_id, || { - self.window.next_frame.view_stack.push(view_id); - let result = f(self); - self.window.next_frame.view_stack.pop(); - result + if self.window.next_frame.view_stack.last() == Some(&view_id) { + return f(self); + } else { + self.window.next_frame.view_stack.push(view_id); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + } }) } /// Invoke the given function with the given view id present on the view stack. /// This is a fairly low-level method used to paint views. pub fn paint_view(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R { - self.with_view_id(view_id, |cx| { - cx.window - .next_frame - .dispatch_tree - .push_node(None, None, Some(view_id)); - let result = f(cx); - cx.window.next_frame.dispatch_tree.pop_node(); - result + let text_system = self.text_system().clone(); + text_system.with_view(view_id, || { + if self.window.next_frame.view_stack.last() == Some(&view_id) { + return f(self); + } else { + self.window.next_frame.view_stack.push(view_id); + self.window + .next_frame + .dispatch_tree + .push_node(None, None, Some(view_id)); + let result = f(self); + self.window.next_frame.view_stack.pop(); + result + } }) } From 981858ef3c280043524c30b9d44f47b5c71a7f2e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 10:01:18 -0700 Subject: [PATCH 154/334] Fix panic with many participants --- crates/ui/src/styles/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui/src/styles/color.rs b/crates/ui/src/styles/color.rs index 434183e5606135cdcb7e420c023c1108d0aa0a42..1c9fd789d958a1d0b886ab23e62232fc219589d3 100644 --- a/crates/ui/src/styles/color.rs +++ b/crates/ui/src/styles/color.rs @@ -37,7 +37,7 @@ impl Color { Color::Info => cx.theme().status().info, Color::Placeholder => cx.theme().colors().text_placeholder, Color::Accent => cx.theme().colors().text_accent, - Color::Player(i) => cx.theme().styles.player.0[i.clone() as usize].cursor, + Color::Player(i) => cx.theme().styles.player.color_for_participant(*i).cursor, Color::Error => cx.theme().status().error, Color::Selected => cx.theme().colors().text_accent, Color::Success => cx.theme().status().success, From f4a7f6f4c3cf0d257387464bef1c107b0c127258 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Jan 2024 18:06:18 +0100 Subject: [PATCH 155/334] Fix warning --- crates/editor/src/element.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a2f1b0c09dd1f48bef0d8111054756d48e433589..f5c016a7c78cbee00bd5e606b3f8999fcb1a3661 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3595,7 +3595,7 @@ mod tests { // Don't panic. let bounds = Bounds::::new(Default::default(), size); - cx.update_window(window.into(), |view, cx| element.paint(bounds, &mut (), cx)) + cx.update_window(window.into(), |_, cx| element.paint(bounds, &mut (), cx)) .unwrap() } From dc158f708f2334205feee39c8ccb7c57edd573c1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 10:23:22 -0700 Subject: [PATCH 156/334] Update chat panel with current channel --- crates/call/src/call.rs | 2 ++ crates/call/src/room.rs | 3 +++ crates/collab_ui/src/chat_panel.rs | 14 +++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 87d2e9aa78df53676d003de0fd044cbab797bc5a..a37cc3bc789a569e86e770712a1cc8ec58820352 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -442,6 +442,8 @@ impl ActiveCall { .location .as_ref() .and_then(|location| location.upgrade()); + let channel_id = room.update(cx, |room, cx| room.channel_id()); + cx.emit(Event::RoomJoined { channel_id }); room.update(cx, |room, cx| room.set_location(location.as_ref(), cx)) } } else { diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 45c6c15fb00a4cc42131d7b3acfa8524201044c1..6dae1e6a4d5e7ad5ba0b8b96a5622fa323f4194c 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -26,6 +26,9 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { + RoomJoined { + channel_id: Option, + }, ParticipantLocationChanged { participant_id: proto::PeerId, }, diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 5786ab10d4ca59b998b1b16ea7bb3c53611b4399..bbe0a6b4fe39541c12bf66db246f6a039fe6850d 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,6 +1,6 @@ use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; use anyhow::Result; -use call::ActiveCall; +use call::{room, ActiveCall}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; use client::Client; use collections::HashMap; @@ -140,6 +140,18 @@ impl ChatPanel { cx.notify(); }, )); + this.subscriptions.push(cx.subscribe( + &ActiveCall::global(cx), + move |this: &mut Self, _, event: &room::Event, cx| match event { + room::Event::RoomJoined { channel_id } => { + if let Some(channel_id) = channel_id { + this.select_channel(*channel_id, None, cx) + .detach_and_log_err(cx) + } + } + _ => {} + }, + )); this }) From f0d490c671400c75cfb87a267729610bf6998a3e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 10:34:24 -0700 Subject: [PATCH 157/334] Open chat panel for guests --- crates/call/src/call.rs | 6 ++++-- crates/call/src/room.rs | 1 + crates/collab_ui/src/chat_panel.rs | 9 +++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index a37cc3bc789a569e86e770712a1cc8ec58820352..72bb6a4e6e81ddd6dad6bcd78a8e4125130fa37e 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -442,8 +442,10 @@ impl ActiveCall { .location .as_ref() .and_then(|location| location.upgrade()); - let channel_id = room.update(cx, |room, cx| room.channel_id()); - cx.emit(Event::RoomJoined { channel_id }); + let (channel_id, role) = room.update(cx, |room, _| { + (room.channel_id(), room.local_participant().role) + }); + cx.emit(Event::RoomJoined { channel_id, role }); room.update(cx, |room, cx| room.set_location(location.as_ref(), cx)) } } else { diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 6dae1e6a4d5e7ad5ba0b8b96a5622fa323f4194c..23a6ea4a1cd44eba03db92f40dca867eafb7a9f3 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -28,6 +28,7 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub enum Event { RoomJoined { channel_id: Option, + role: proto::ChannelRole, }, ParticipantLocationChanged { participant_id: proto::PeerId, diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index bbe0a6b4fe39541c12bf66db246f6a039fe6850d..a170f956978f1a8a999f3c5460db4d103f18cb3e 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -16,6 +16,7 @@ use menu::Confirm; use message_editor::MessageEditor; use project::Fs; use rich_text::RichText; +use rpc::proto; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; @@ -143,10 +144,14 @@ impl ChatPanel { this.subscriptions.push(cx.subscribe( &ActiveCall::global(cx), move |this: &mut Self, _, event: &room::Event, cx| match event { - room::Event::RoomJoined { channel_id } => { + room::Event::RoomJoined { channel_id, role } => { if let Some(channel_id) = channel_id { this.select_channel(*channel_id, None, cx) - .detach_and_log_err(cx) + .detach_and_log_err(cx); + + if *role == proto::ChannelRole::Guest { + cx.emit(PanelEvent::Activate) + } } } _ => {} From 7dc28aad787ae3f88f2c6c95979358ba170c0a2e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Jan 2024 10:38:05 -0800 Subject: [PATCH 158/334] Forbid paste, undo, redo on read-only editors --- crates/editor/src/editor.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7fe942f14561c8781b17c9ae66ea356190425422..66d30b049cb396651368d77c345c7b50e5c6554d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5443,6 +5443,10 @@ impl Editor { } pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } + self.transact(cx, |this, cx| { if let Some(item) = cx.read_from_clipboard() { let clipboard_text = Cow::Borrowed(item.text()); @@ -5515,6 +5519,10 @@ impl Editor { } pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } + if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) { if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() { self.change_selections(None, cx, |s| { @@ -5529,6 +5537,10 @@ impl Editor { } pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext) { + if self.read_only(cx) { + return; + } + if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) { if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned() { From 551fd9ba7ee3d5a4c51da110a140965bb25f8569 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 12:40:09 -0700 Subject: [PATCH 159/334] Boop --- .../gpui/src/platform/mac/metal_renderer.rs | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index a6cdd166d312d5eb97fc749a645a8cb09ea0dd8a..2a1f9ef92def62b85b436f053c37e74c9600c00e 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. +const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. [] pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -429,6 +429,13 @@ impl MetalRenderer { let shadow_bytes_len = std::mem::size_of_val(shadows); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + shadow_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + unsafe { ptr::copy_nonoverlapping( shadows.as_ptr() as *const u8, @@ -437,12 +444,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + shadow_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, @@ -489,15 +490,15 @@ impl MetalRenderer { let quad_bytes_len = std::mem::size_of_val(quads); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - unsafe { - ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); - } let next_offset = *offset + quad_bytes_len; assert!( next_offset <= INSTANCE_BUFFER_SIZE, "instance buffer exhausted" ); + unsafe { + ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); + } command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -586,23 +587,33 @@ impl MetalRenderer { command_encoder .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); + // hypothesis: sprites.as_ptr() does something bogus sometimes? + // let sprite_bytes_len = mem::size_of::() * sprites.len(); + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - unsafe { - ptr::copy_nonoverlapping( - sprites.as_ptr() as *const u8, - buffer_contents, - sprite_bytes_len, - ); - } + // buffer_contents.len() < spite_bytes_len must be out of range. + // PANIC HERE! let next_offset = *offset + sprite_bytes_len; assert!( next_offset <= INSTANCE_BUFFER_SIZE, "instance buffer exhausted" ); + unsafe { + ptr::copy_nonoverlapping( + sprites.as_ptr() as *const u8, //src + buffer_contents, //dest + sprite_bytes_len, // count + ); + } + command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, @@ -723,6 +734,13 @@ impl MetalRenderer { let sprite_bytes_len = std::mem::size_of_val(sprites); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -731,12 +749,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, @@ -794,6 +806,12 @@ impl MetalRenderer { let sprite_bytes_len = std::mem::size_of_val(sprites); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -802,12 +820,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, From 324d1d119ba4066064bd13505053b308ffc9ae07 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 12:40:37 -0800 Subject: [PATCH 160/334] Add some context to assert --- .../gpui/src/platform/mac/metal_renderer.rs | 70 +++++++------------ 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 2a1f9ef92def62b85b436f053c37e74c9600c00e..8365525ebd905902dbed3a8eea815ca298566a82 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. [] +const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -337,10 +337,7 @@ impl MetalRenderer { for (texture_id, vertices) in vertices_by_texture_id { align_offset(offset); let next_offset = *offset + vertices.len() * mem::size_of::>(); - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, vertices.len(), "Path Vertexes"); let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -431,10 +428,7 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + shadow_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, shadows.len(), "Shadows"); unsafe { ptr::copy_nonoverlapping( @@ -492,10 +486,8 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + quad_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, quads.len(), "Quads"); + unsafe { ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); } @@ -587,30 +579,18 @@ impl MetalRenderer { command_encoder .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); - // hypothesis: sprites.as_ptr() does something bogus sometimes? - // let sprite_bytes_len = mem::size_of::() * sprites.len(); let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Path Sprites"); + let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - // buffer_contents.len() < spite_bytes_len must be out of range. - // PANIC HERE! - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - unsafe { ptr::copy_nonoverlapping( - sprites.as_ptr() as *const u8, //src - buffer_contents, //dest - sprite_bytes_len, // count + sprites.as_ptr() as *const u8, + buffer_contents, + sprite_bytes_len, ); } @@ -672,10 +652,7 @@ impl MetalRenderer { } let next_offset = *offset + quad_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, underlines.len(), "Underlines"); command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -736,10 +713,7 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Monoschrome Sprites"); unsafe { ptr::copy_nonoverlapping( @@ -808,10 +782,8 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Polychrome Sprites"); + unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -886,10 +858,7 @@ impl MetalRenderer { align_offset(offset); let next_offset = *offset + mem::size_of::(); - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, 1, "Surface"); command_encoder.set_vertex_buffer( SurfaceInputIndex::Surfaces as u64, @@ -926,6 +895,15 @@ impl MetalRenderer { *offset = next_offset; } } + + fn assert_instance_buffer_bounds(&self, next_offset: usize, count: usize, item: &'static str) { + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted attempting to copy {} of {}", + count, + item + ); + } } fn build_pipeline_state( From aa5c6a8aa354e424a60f736559355045b091775c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 14:35:50 -0700 Subject: [PATCH 161/334] Update graphics memory assert to be more helpful --- .../gpui/src/platform/mac/metal_renderer.rs | 192 ++++++++++-------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 8365525ebd905902dbed3a8eea815ca298566a82..20e749a2f607fa96ec6fbdfc76574307648a621d 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. +const INSTANCE_BUFFER_SIZE: usize = 32 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...) pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -204,7 +204,11 @@ impl MetalRenderer { let command_buffer = command_queue.new_command_buffer(); let mut instance_offset = 0; - let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, command_buffer); + let Some(path_tiles) = + self.rasterize_paths(scene.paths(), &mut instance_offset, command_buffer) + else { + panic!("failed to rasterize {} paths", scene.paths().len()); + }; let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -228,67 +232,67 @@ impl MetalRenderer { zfar: 1.0, }); for batch in scene.batches() { - match batch { - PrimitiveBatch::Shadows(shadows) => { - self.draw_shadows( - shadows, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + let ok = match batch { + PrimitiveBatch::Shadows(shadows) => self.draw_shadows( + shadows, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::Quads(quads) => { - self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder); - } - PrimitiveBatch::Paths(paths) => { - self.draw_paths( - paths, - &path_tiles, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } - PrimitiveBatch::Underlines(underlines) => { - self.draw_underlines( - underlines, - &mut instance_offset, - viewport_size, - command_encoder, - ); + self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder) } + PrimitiveBatch::Paths(paths) => self.draw_paths( + paths, + &path_tiles, + &mut instance_offset, + viewport_size, + command_encoder, + ), + PrimitiveBatch::Underlines(underlines) => self.draw_underlines( + underlines, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::MonochromeSprites { texture_id, sprites, - } => { - self.draw_monochrome_sprites( - texture_id, - sprites, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + } => self.draw_monochrome_sprites( + texture_id, + sprites, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::PolychromeSprites { texture_id, sprites, - } => { - self.draw_polychrome_sprites( - texture_id, - sprites, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } - PrimitiveBatch::Surfaces(surfaces) => { - self.draw_surfaces( - surfaces, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + } => self.draw_polychrome_sprites( + texture_id, + sprites, + &mut instance_offset, + viewport_size, + command_encoder, + ), + PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces( + surfaces, + &mut instance_offset, + viewport_size, + command_encoder, + ), + }; + + if !ok { + panic!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces", + scene.paths.len(), + scene.shadows.len(), + scene.quads.len(), + scene.underlines.len(), + scene.monochrome_sprites.len(), + scene.polychrome_sprites.len(), + scene.surfaces.len(), + ) } } @@ -311,7 +315,7 @@ impl MetalRenderer { paths: &[Path], offset: &mut usize, command_buffer: &metal::CommandBufferRef, - ) -> HashMap { + ) -> Option> { let mut tiles = HashMap::default(); let mut vertices_by_texture_id = HashMap::default(); for path in paths { @@ -337,7 +341,9 @@ impl MetalRenderer { for (texture_id, vertices) in vertices_by_texture_id { align_offset(offset); let next_offset = *offset + vertices.len() * mem::size_of::>(); - self.assert_instance_buffer_bounds(next_offset, vertices.len(), "Path Vertexes"); + if next_offset > INSTANCE_BUFFER_SIZE { + return None; + } let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -386,7 +392,7 @@ impl MetalRenderer { *offset = next_offset; } - tiles + Some(tiles) } fn draw_shadows( @@ -395,9 +401,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if shadows.is_empty() { - return; + return true; } align_offset(offset); @@ -428,7 +434,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + shadow_bytes_len; - self.assert_instance_buffer_bounds(next_offset, shadows.len(), "Shadows"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -445,6 +453,7 @@ impl MetalRenderer { shadows.len() as u64, ); *offset = next_offset; + true } fn draw_quads( @@ -453,9 +462,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if quads.is_empty() { - return; + return true; } align_offset(offset); @@ -486,7 +495,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + quad_bytes_len; - self.assert_instance_buffer_bounds(next_offset, quads.len(), "Quads"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); @@ -499,6 +510,7 @@ impl MetalRenderer { quads.len() as u64, ); *offset = next_offset; + true } fn draw_paths( @@ -508,9 +520,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if paths.is_empty() { - return; + return true; } command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state); @@ -581,7 +593,9 @@ impl MetalRenderer { let sprite_bytes_len = mem::size_of::() * sprites.len(); let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Path Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; @@ -604,6 +618,7 @@ impl MetalRenderer { sprites.clear(); } } + true } fn draw_underlines( @@ -612,9 +627,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if underlines.is_empty() { - return; + return true; } align_offset(offset); @@ -652,7 +667,9 @@ impl MetalRenderer { } let next_offset = *offset + quad_bytes_len; - self.assert_instance_buffer_bounds(next_offset, underlines.len(), "Underlines"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -661,6 +678,7 @@ impl MetalRenderer { underlines.len() as u64, ); *offset = next_offset; + true } fn draw_monochrome_sprites( @@ -670,9 +688,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if sprites.is_empty() { - return; + return true; } align_offset(offset); @@ -713,7 +731,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Monoschrome Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -730,6 +750,7 @@ impl MetalRenderer { sprites.len() as u64, ); *offset = next_offset; + true } fn draw_polychrome_sprites( @@ -739,9 +760,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if sprites.is_empty() { - return; + return true; } align_offset(offset); @@ -782,7 +803,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Polychrome Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -799,6 +822,7 @@ impl MetalRenderer { sprites.len() as u64, ); *offset = next_offset; + true } fn draw_surfaces( @@ -807,7 +831,7 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state); command_encoder.set_vertex_buffer( SurfaceInputIndex::Vertices as u64, @@ -858,7 +882,9 @@ impl MetalRenderer { align_offset(offset); let next_offset = *offset + mem::size_of::(); - self.assert_instance_buffer_bounds(next_offset, 1, "Surface"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } command_encoder.set_vertex_buffer( SurfaceInputIndex::Surfaces as u64, @@ -894,15 +920,7 @@ impl MetalRenderer { command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6); *offset = next_offset; } - } - - fn assert_instance_buffer_bounds(&self, next_offset: usize, count: usize, item: &'static str) { - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted attempting to copy {} of {}", - count, - item - ); + true } } From 50f3bbbc8b38bec8e687db8bcebd1667454450c0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 15:08:14 -0700 Subject: [PATCH 162/334] Open chat when joining a channel with guests (and close it when leaving a channel again) --- crates/call/src/call.rs | 6 ++---- crates/call/src/room.rs | 17 ++++++++++++--- crates/collab_ui/src/chat_panel.rs | 21 ++++++++++++++++--- .../project_shared_notification.rs | 2 +- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 72bb6a4e6e81ddd6dad6bcd78a8e4125130fa37e..6d57a42ff7e689cb51140f89d0ef7f8863e394d2 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -442,10 +442,8 @@ impl ActiveCall { .location .as_ref() .and_then(|location| location.upgrade()); - let (channel_id, role) = room.update(cx, |room, _| { - (room.channel_id(), room.local_participant().role) - }); - cx.emit(Event::RoomJoined { channel_id, role }); + let channel_id = room.read(cx).channel_id(); + cx.emit(Event::RoomJoined { channel_id }); room.update(cx, |room, cx| room.set_location(location.as_ref(), cx)) } } else { diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 23a6ea4a1cd44eba03db92f40dca867eafb7a9f3..e17a3667bae639186063685923a53a71867ffa27 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -28,7 +28,6 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub enum Event { RoomJoined { channel_id: Option, - role: proto::ChannelRole, }, ParticipantLocationChanged { participant_id: proto::PeerId, @@ -53,7 +52,9 @@ pub enum Event { RemoteProjectInvitationDiscarded { project_id: u64, }, - Left, + Left { + channel_id: Option, + }, } pub struct Room { @@ -361,7 +362,9 @@ impl Room { pub(crate) fn leave(&mut self, cx: &mut ModelContext) -> Task> { cx.notify(); - cx.emit(Event::Left); + cx.emit(Event::Left { + channel_id: self.channel_id(), + }); self.leave_internal(cx) } @@ -602,6 +605,14 @@ impl Room { .map(|participant| participant.role) } + pub fn contains_guests(&self) -> bool { + self.local_participant.role == proto::ChannelRole::Guest + || self + .remote_participants + .values() + .any(|p| p.role == proto::ChannelRole::Guest) + } + pub fn local_participant_is_admin(&self) -> bool { self.local_participant.role == proto::ChannelRole::Admin } diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index a170f956978f1a8a999f3c5460db4d103f18cb3e..f8ee12ef814d3ebb98e69bad087252fda7a621d9 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -143,17 +143,26 @@ impl ChatPanel { )); this.subscriptions.push(cx.subscribe( &ActiveCall::global(cx), - move |this: &mut Self, _, event: &room::Event, cx| match event { - room::Event::RoomJoined { channel_id, role } => { + move |this: &mut Self, call, event: &room::Event, cx| match event { + room::Event::RoomJoined { channel_id } => { if let Some(channel_id) = channel_id { this.select_channel(*channel_id, None, cx) .detach_and_log_err(cx); - if *role == proto::ChannelRole::Guest { + if call + .read(cx) + .room() + .is_some_and(|room| room.read(cx).contains_guests()) + { cx.emit(PanelEvent::Activate) } } } + room::Event::Left { channel_id } => { + if channel_id == &this.channel_id(cx) { + cx.emit(PanelEvent::Close) + } + } _ => {} }, )); @@ -162,6 +171,12 @@ impl ChatPanel { }) } + pub fn channel_id(&self, cx: &AppContext) -> Option { + self.active_chat + .as_ref() + .map(|(chat, _)| chat.read(cx).channel_id) + } + pub fn is_scrolled_to_bottom(&self) -> bool { self.is_scrolled_to_bottom } diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index 88fe540c397b65c8ddc3ad0230c47210b8bc0e7e..b8ceefcd765f4e1797bcf1584ac93594a1fffdaa 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -58,7 +58,7 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { } } - room::Event::Left => { + room::Event::Left { .. } => { for (_, windows) in notification_windows.drain() { for window in windows { window From cdc227b32fd23f979342a34cd519816ce4b44728 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 12 Jan 2024 17:44:15 -0500 Subject: [PATCH 163/334] Still paint group hover handler for invisible divs Fixes bug where tab close icon show on hover is inconsistent --- crates/gpui/src/elements/div.rs | 37 +++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 627a2ac339d631c666926d4fc8e1354396cb36f7..0c1b0b74bb33fd6d06068463dee0c80b87573e98 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -978,12 +978,31 @@ impl Interactivity { f: impl FnOnce(&Style, Point, &mut WindowContext), ) { let style = self.compute_style(Some(bounds), element_state, cx); + let z_index = style.z_index.unwrap_or(0); + + let paint_hover_group_handler = |cx: &mut WindowContext| { + let hover_group_bounds = self + .group_hover_style + .as_ref() + .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); + + if let Some(group_bounds) = hover_group_bounds { + let hovered = group_bounds.contains(&cx.mouse_position()); + cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture + && group_bounds.contains(&event.position) != hovered + { + cx.notify(); + } + }); + } + }; if style.visibility == Visibility::Hidden { + cx.with_z_index(z_index, |cx| paint_hover_group_handler(cx)); return; } - let z_index = style.z_index.unwrap_or(0); cx.with_z_index(z_index, |cx| { style.paint(bounds, cx, |cx| { cx.with_text_style(style.text_style().cloned(), |cx| { @@ -1166,21 +1185,7 @@ impl Interactivity { }) } - let hover_group_bounds = self - .group_hover_style - .as_ref() - .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); - - if let Some(group_bounds) = hover_group_bounds { - let hovered = group_bounds.contains(&cx.mouse_position()); - cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture - && group_bounds.contains(&event.position) != hovered - { - cx.notify(); - } - }); - } + paint_hover_group_handler(cx); if self.hover_style.is_some() || self.base_style.mouse_cursor.is_some() From aa50f699400ca66869fa3007cdebd4587c2ba1aa Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Jan 2024 14:46:17 -0800 Subject: [PATCH 164/334] Add LiveKit APIs for starting and stopping audio tracks --- .../Sources/LiveKitBridge/LiveKitBridge.swift | 12 ++++++++++++ crates/live_kit_client/src/prod.rs | 14 +++++++------- crates/live_kit_client/src/test.rs | 10 ++++++---- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift index db5da8e0e9ec5608e81fe34f3aa901d2e819f21d..7468c08791f8b0782cf711cb9b546bf2940ae6fe 100644 --- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -286,6 +286,18 @@ public func LKRemoteAudioTrackGetSid(track: UnsafeRawPointer) -> CFString { return track.sid! as CFString } +@_cdecl("LKRemoteAudioTrackStart") +public func LKRemoteAudioTrackStart(track: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + track.start() +} + +@_cdecl("LKRemoteAudioTrackStop") +public func LKRemoteAudioTrackStop(track: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + track.stop() +} + @_cdecl("LKDisplaySources") public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) { MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs index f1660cc3d1a7ce9f7951cfd5f1a354158a41d334..a4bd9d4f07b6c88f3b7fe7dca343a972659d975f 100644 --- a/crates/live_kit_client/src/prod.rs +++ b/crates/live_kit_client/src/prod.rs @@ -18,8 +18,6 @@ use std::{ sync::{Arc, Weak}, }; -// SAFETY: Most live kit types are threadsafe: -// https://github.com/livekit/client-sdk-swift#thread-safety macro_rules! pointer_type { ($pointer_name:ident) => { #[repr(transparent)] @@ -134,8 +132,10 @@ extern "C" { ) -> *const c_void; fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef; - fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void); fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef; + fn LKRemoteAudioTrackStart(track: swift::RemoteAudioTrack); + fn LKRemoteAudioTrackStop(track: swift::RemoteAudioTrack); + fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void); fn LKDisplaySources( callback_data: *mut c_void, @@ -853,12 +853,12 @@ impl RemoteAudioTrack { &self.publisher_id } - pub fn enable(&self) -> impl Future> { - async { Ok(()) } + pub fn start(&self) { + unsafe { LKRemoteAudioTrackStart(self.native_track) } } - pub fn disable(&self) -> impl Future> { - async { Ok(()) } + pub fn stop(&self) { + unsafe { LKRemoteAudioTrackStop(self.native_track) } } } diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 0716042ff196e1e0ea8f3543f03b645648ed473c..1b7fd20bc257ca5559a37b13ed70b05fe8eb90f7 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -262,6 +262,7 @@ impl TestServer { let track = Arc::new(RemoteAudioTrack { sid: sid.clone(), publisher_id: identity.clone(), + running: AtomicBool::new(true), }); let publication = Arc::new(RemoteTrackPublication); @@ -644,6 +645,7 @@ impl RemoteVideoTrack { pub struct RemoteAudioTrack { sid: Sid, publisher_id: Sid, + running: AtomicBool, } impl RemoteAudioTrack { @@ -655,12 +657,12 @@ impl RemoteAudioTrack { &self.publisher_id } - pub fn enable(&self) -> impl Future> { - async { Ok(()) } + pub fn start(&self) { + self.running.store(true, SeqCst); } - pub fn disable(&self) -> impl Future> { - async { Ok(()) } + pub fn stop(&self) { + self.running.store(false, SeqCst); } } From eafe0944e0b4b8c0a23f6fef9f90e6b56fd06710 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 16:01:51 -0700 Subject: [PATCH 165/334] Some tweaks for chat panels --- crates/collab_ui/src/chat_panel.rs | 189 +++++++++++------------------ 1 file changed, 69 insertions(+), 120 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index f8ee12ef814d3ebb98e69bad087252fda7a621d9..ecd7d1a739e957bfa7c7bf7b19de774e97675e64 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{channel_view::ChannelView, is_channels_feature_enabled, ChatPanelSettings}; +use crate::{collab_panel, is_channels_feature_enabled, ChatPanelSettings, CollabPanel}; use anyhow::Result; use call::{room, ActiveCall}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -7,9 +7,9 @@ use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ - actions, div, list, prelude::*, px, AnyElement, AppContext, AsyncWindowContext, ClickEvent, - ElementId, EventEmitter, FocusableView, ListOffset, ListScrollEvent, ListState, Model, Render, - Subscription, Task, View, ViewContext, VisualContext, WeakView, + actions, div, list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, + ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView, ListOffset, ListScrollEvent, + ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use language::LanguageRegistry; use menu::Confirm; @@ -21,7 +21,9 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; -use ui::{prelude::*, Avatar, Button, IconButton, IconName, Label, TabBar, Tooltip}; +use ui::{ + prelude::*, Avatar, Button, IconButton, IconName, Key, KeyBinding, Label, TabBar, Tooltip, +}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -55,7 +57,6 @@ pub struct ChatPanel { active: bool, pending_serialization: Task>, subscriptions: Vec, - workspace: WeakView, is_scrolled_to_bottom: bool, markdown_data: HashMap, } @@ -90,8 +91,6 @@ impl ChatPanel { ) }); - let workspace_handle = workspace.weak_handle(); - cx.new_view(|cx: &mut ViewContext| { let view = cx.view().downgrade(); let message_list = @@ -123,7 +122,6 @@ impl ChatPanel { message_editor: input_editor, local_timezone: cx.local_timezone(), subscriptions: Vec::new(), - workspace: workspace_handle, is_scrolled_to_bottom: true, active: false, width: None, @@ -291,50 +289,6 @@ impl ChatPanel { } } - fn render_channel(&self, cx: &mut ViewContext) -> AnyElement { - v_stack() - .full() - .on_action(cx.listener(Self::send)) - .child( - h_stack().z_index(1).child( - TabBar::new("chat_header") - .child( - h_stack() - .w_full() - .h(rems(ui::Tab::HEIGHT_IN_REMS)) - .px_2() - .child(Label::new( - self.active_chat - .as_ref() - .and_then(|c| { - Some(format!("#{}", c.0.read(cx).channel(cx)?.name)) - }) - .unwrap_or_default(), - )), - ) - .end_child( - IconButton::new("notes", IconName::File) - .on_click(cx.listener(Self::open_notes)) - .tooltip(|cx| Tooltip::text("Open notes", cx)), - ) - .end_child( - IconButton::new("call", IconName::AudioOn) - .on_click(cx.listener(Self::join_call)) - .tooltip(|cx| Tooltip::text("Join call", cx)), - ), - ), - ) - .child(div().flex_grow().px_2().py_1().map(|this| { - if self.active_chat.is_some() { - this.child(list(self.message_list.clone()).full()) - } else { - this - } - })) - .child(h_stack().p_2().child(self.message_editor.clone())) - .into_any() - } - fn render_message(&mut self, ix: usize, cx: &mut ViewContext) -> impl IntoElement { let active_chat = &self.active_chat.as_ref().unwrap().0; let (message, is_continuation_from_previous, is_continuation_to_next, is_admin) = @@ -453,44 +407,6 @@ impl ChatPanel { rich_text::render_markdown(message.body.clone(), &mentions, language_registry, None) } - fn render_sign_in_prompt(&self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() - .gap_2() - .p_4() - .child( - Button::new("sign-in", "Sign in") - .style(ButtonStyle::Filled) - .icon_color(Color::Muted) - .icon(IconName::Github) - .icon_position(IconPosition::Start) - .full_width() - .on_click(cx.listener(move |this, _, cx| { - let client = this.client.clone(); - cx.spawn(|this, mut cx| async move { - if client - .authenticate_and_connect(true, &cx) - .log_err() - .await - .is_some() - { - this.update(&mut cx, |_, cx| { - cx.focus_self(); - }) - .ok(); - } - }) - .detach(); - })), - ) - .child( - div().flex().w_full().items_center().child( - Label::new("Sign in to chat.") - .color(Color::Muted) - .size(LabelSize::Small), - ), - ) - } - fn send(&mut self, _: &Confirm, cx: &mut ViewContext) { if let Some((chat, _)) = self.active_chat.as_ref() { let message = self @@ -567,24 +483,6 @@ impl ChatPanel { Ok(()) }) } - - fn open_notes(&mut self, _: &ClickEvent, cx: &mut ViewContext) { - if let Some((chat, _)) = &self.active_chat { - let channel_id = chat.read(cx).channel_id; - if let Some(workspace) = self.workspace.upgrade() { - ChannelView::open(channel_id, workspace, cx).detach(); - } - } - } - - fn join_call(&mut self, _: &ClickEvent, cx: &mut ViewContext) { - if let Some((chat, _)) = &self.active_chat { - let channel_id = chat.read(cx).channel_id; - ActiveCall::global(cx) - .update(cx, |call, cx| call.join_channel(channel_id, cx)) - .detach_and_log_err(cx); - } - } } impl EventEmitter for ChatPanel {} @@ -592,19 +490,70 @@ impl EventEmitter for ChatPanel {} impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_stack() - .size_full() - .map(|this| match (self.client.user_id(), self.active_chat()) { - (Some(_), Some(_)) => this.child(self.render_channel(cx)), - (Some(_), None) => this.child( - div().p_4().child( - Label::new("Select a channel to chat in.") - .size(LabelSize::Small) - .color(Color::Muted), + .full() + .on_action(cx.listener(Self::send)) + .child( + h_stack().z_index(1).child( + TabBar::new("chat_header").child( + h_stack() + .w_full() + .h(rems(ui::Tab::HEIGHT_IN_REMS)) + .px_2() + .child(Label::new( + self.active_chat + .as_ref() + .and_then(|c| { + Some(format!("#{}", c.0.read(cx).channel(cx)?.name)) + }) + .unwrap_or("Chat".to_string()), + )), ), ), - (None, _) => this.child(self.render_sign_in_prompt(cx)), - }) - .min_w(px(150.)) + ) + .child(div().flex_grow().px_2().py_1().map(|this| { + if self.active_chat.is_some() { + this.child(list(self.message_list.clone()).full()) + } else { + this.child( + div() + .p_4() + .child( + Label::new("Select a channel to chat in.") + .size(LabelSize::Small) + .color(Color::Muted), + ) + .child( + div().pt_1().w_full().items_center().child( + Button::new("toggle-collab", "Open") + .full_width() + .key_binding(KeyBinding::for_action( + &collab_panel::ToggleFocus, + cx, + )) + .on_click(|_, cx| { + cx.dispatch_action( + collab_panel::ToggleFocus.boxed_clone(), + ) + }), + ), + ), + ) + } + })) + .child(h_stack().p_2().map(|el| { + if self.active_chat.is_some() { + el.child(self.message_editor.clone()) + } else { + el.child( + div() + .rounded_md() + .h_7() + .w_full() + .bg(cx.theme().colors().editor_background), + ) + } + })) + .into_any() } } From c2cf28804a761e21ef1007fbf150aecd0b368c52 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Jan 2024 15:12:29 -0800 Subject: [PATCH 166/334] Don't run newly published audio tracks when deafened Also, simplify the management of the muted and deafened state in Room. --- crates/call/src/room.rs | 263 +++++++++---------- crates/collab_ui/src/collab_titlebar_item.rs | 4 +- crates/collab_ui/src/collab_ui.rs | 13 +- 3 files changed, 133 insertions(+), 147 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 45c6c15fb00a4cc42131d7b3acfa8524201044c1..0979ad8bb93092ad5e5d75506bff4872f723da08 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -150,17 +150,14 @@ impl Room { let connect = room.connect(&connection_info.server_url, &connection_info.token); cx.spawn(|this, mut cx| async move { connect.await?; - - let is_read_only = this - .update(&mut cx, |room, _| room.read_only()) - .unwrap_or(true); - - if !cx.update(|cx| Self::mute_on_join(cx))? && !is_read_only { - this.update(&mut cx, |this, cx| this.share_microphone(cx))? - .await?; - } - - anyhow::Ok(()) + this.update(&mut cx, |this, cx| { + if this.read_only() || this.is_muted() { + Task::ready(Ok(())) + } else { + this.share_microphone(cx) + } + })? + .await }) .detach_and_log_err(cx); @@ -169,7 +166,7 @@ impl Room { screen_track: LocalTrack::None, microphone_track: LocalTrack::None, next_publish_id: 0, - muted_by_user: false, + muted_by_user: Self::mute_on_join(cx), deafened: false, speaking: false, _maintain_room, @@ -1032,6 +1029,15 @@ impl Room { } RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => { + if let Some(live_kit) = &self.live_kit { + if live_kit.deafened { + track.stop(); + cx.foreground_executor() + .spawn(publication.set_enabled(false)) + .detach(); + } + } + let user_id = track.publisher_id().parse()?; let track_id = track.sid().to_string(); let participant = self @@ -1286,15 +1292,12 @@ impl Room { }) } - pub fn is_muted(&self, cx: &AppContext) -> bool { - self.live_kit - .as_ref() - .and_then(|live_kit| match &live_kit.microphone_track { - LocalTrack::None => Some(Self::mute_on_join(cx)), - LocalTrack::Pending { muted, .. } => Some(*muted), - LocalTrack::Published { muted, .. } => Some(*muted), - }) - .unwrap_or(false) + pub fn is_muted(&self) -> bool { + self.live_kit.as_ref().map_or(false, |live_kit| { + matches!(live_kit.microphone_track, LocalTrack::None) + || live_kit.muted_by_user + || live_kit.deafened + }) } pub fn read_only(&self) -> bool { @@ -1316,16 +1319,11 @@ impl Room { pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); - } else if self.is_sharing_mic() { - return Task::ready(Err(anyhow!("microphone was already shared"))); } let publish_id = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); - live_kit.microphone_track = LocalTrack::Pending { - publish_id, - muted: false, - }; + live_kit.microphone_track = LocalTrack::Pending { publish_id }; cx.notify(); publish_id } else { @@ -1354,14 +1352,13 @@ impl Room { .as_mut() .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - let (canceled, muted) = if let LocalTrack::Pending { + let canceled = if let LocalTrack::Pending { publish_id: cur_publish_id, - muted, } = &live_kit.microphone_track { - (*cur_publish_id != publish_id, *muted) + *cur_publish_id != publish_id } else { - (true, false) + true }; match publication { @@ -1369,14 +1366,13 @@ impl Room { if canceled { live_kit.room.unpublish_track(publication); } else { - if muted { + if live_kit.muted_by_user || live_kit.deafened { cx.background_executor() - .spawn(publication.set_mute(muted)) + .spawn(publication.set_mute(true)) .detach(); } live_kit.microphone_track = LocalTrack::Published { track_publication: publication, - muted, }; cx.notify(); } @@ -1405,10 +1401,7 @@ impl Room { let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); - live_kit.screen_track = LocalTrack::Pending { - publish_id, - muted: false, - }; + live_kit.screen_track = LocalTrack::Pending { publish_id }; cx.notify(); (live_kit.room.display_sources(), publish_id) } else { @@ -1442,14 +1435,13 @@ impl Room { .as_mut() .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - let (canceled, muted) = if let LocalTrack::Pending { + let canceled = if let LocalTrack::Pending { publish_id: cur_publish_id, - muted, } = &live_kit.screen_track { - (*cur_publish_id != publish_id, *muted) + *cur_publish_id != publish_id } else { - (true, false) + true }; match publication { @@ -1457,14 +1449,8 @@ impl Room { if canceled { live_kit.room.unpublish_track(publication); } else { - if muted { - cx.background_executor() - .spawn(publication.set_mute(muted)) - .detach(); - } live_kit.screen_track = LocalTrack::Published { track_publication: publication, - muted, }; cx.notify(); } @@ -1487,61 +1473,48 @@ impl Room { }) } - pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> Result>> { - let should_mute = !self.is_muted(cx); + pub fn toggle_mute(&mut self, cx: &mut ModelContext) { if let Some(live_kit) = self.live_kit.as_mut() { - if matches!(live_kit.microphone_track, LocalTrack::None) { - return Ok(self.share_microphone(cx)); + // When unmuting, undeafen if the user was deafened before. + let was_deafened = live_kit.deafened; + if live_kit.muted_by_user || live_kit.deafened { + live_kit.muted_by_user = false; + live_kit.deafened = false; + } else { + live_kit.muted_by_user = true; } + let muted = live_kit.muted_by_user; + let should_undeafen = was_deafened && !live_kit.deafened; - let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; - live_kit.muted_by_user = should_mute; + if let Some(task) = self.set_mute(muted, cx) { + task.detach_and_log_err(cx); + } - if old_muted == true && live_kit.deafened == true { - if let Some(task) = self.toggle_deafen(cx).ok() { - task.detach(); + if should_undeafen { + if let Some(task) = self.set_deafened(false, cx) { + task.detach_and_log_err(cx); } } - - Ok(ret_task) - } else { - Err(anyhow!("LiveKit not started")) } } - pub fn toggle_deafen(&mut self, cx: &mut ModelContext) -> Result>> { + pub fn toggle_deafen(&mut self, cx: &mut ModelContext) { if let Some(live_kit) = self.live_kit.as_mut() { - (*live_kit).deafened = !live_kit.deafened; - - let mut tasks = Vec::with_capacity(self.remote_participants.len()); - // Context notification is sent within set_mute itself. - let mut mute_task = None; - // When deafening, mute user's mic as well. - // When undeafening, unmute user's mic unless it was manually muted prior to deafening. - if live_kit.deafened || !live_kit.muted_by_user { - mute_task = Some(live_kit.set_mute(live_kit.deafened, cx)?.0); - }; - for participant in self.remote_participants.values() { - for track in live_kit - .room - .remote_audio_track_publications(&participant.user.id.to_string()) - { - let deafened = live_kit.deafened; - tasks.push(cx.foreground_executor().spawn(track.set_enabled(!deafened))); - } + // When deafening, mute the microphone if it was not already muted. + // When un-deafening, unmute the microphone, unless it was explicitly muted. + let deafened = !live_kit.deafened; + live_kit.deafened = deafened; + let should_change_mute = !live_kit.muted_by_user; + + if let Some(task) = self.set_deafened(deafened, cx) { + task.detach_and_log_err(cx); } - Ok(cx.foreground_executor().spawn(async move { - if let Some(mute_task) = mute_task { - mute_task.await?; - } - for task in tasks { - task.await?; + if should_change_mute { + if let Some(task) = self.set_mute(deafened, cx) { + task.detach_and_log_err(cx); } - Ok(()) - })) - } else { - Err(anyhow!("LiveKit not started")) + } } } @@ -1572,6 +1545,70 @@ impl Room { } } + fn set_deafened( + &mut self, + deafened: bool, + cx: &mut ModelContext, + ) -> Option>> { + let live_kit = self.live_kit.as_mut()?; + cx.notify(); + + let mut track_updates = Vec::new(); + for participant in self.remote_participants.values() { + for publication in live_kit + .room + .remote_audio_track_publications(&participant.user.id.to_string()) + { + track_updates.push(publication.set_enabled(!deafened)); + } + + for track in participant.audio_tracks.values() { + if deafened { + track.stop(); + } else { + track.start(); + } + } + } + + Some(cx.foreground_executor().spawn(async move { + for result in futures::future::join_all(track_updates).await { + result?; + } + Ok(()) + })) + } + + fn set_mute( + &mut self, + should_mute: bool, + cx: &mut ModelContext, + ) -> Option>> { + let live_kit = self.live_kit.as_mut()?; + cx.notify(); + + if should_mute { + Audio::play_sound(Sound::Mute, cx); + } else { + Audio::play_sound(Sound::Unmute, cx); + } + + match &mut live_kit.microphone_track { + LocalTrack::None => { + if should_mute { + None + } else { + Some(self.share_microphone(cx)) + } + } + LocalTrack::Pending { .. } => None, + LocalTrack::Published { track_publication } => Some( + cx.foreground_executor() + .spawn(track_publication.set_mute(should_mute)), + ), + } + } + #[cfg(any(test, feature = "test-support"))] pub fn set_display_sources(&self, sources: Vec) { self.live_kit @@ -1596,50 +1633,6 @@ struct LiveKitRoom { } impl LiveKitRoom { - fn set_mute( - self: &mut LiveKitRoom, - should_mute: bool, - cx: &mut ModelContext, - ) -> Result<(Task>, bool)> { - if !should_mute { - // clear user muting state. - self.muted_by_user = false; - } - - let (result, old_muted) = match &mut self.microphone_track { - LocalTrack::None => Err(anyhow!("microphone was not shared")), - LocalTrack::Pending { muted, .. } => { - let old_muted = *muted; - *muted = should_mute; - cx.notify(); - Ok((Task::Ready(Some(Ok(()))), old_muted)) - } - LocalTrack::Published { - track_publication, - muted, - } => { - let old_muted = *muted; - *muted = should_mute; - cx.notify(); - Ok(( - cx.background_executor() - .spawn(track_publication.set_mute(*muted)), - old_muted, - )) - } - }?; - - if old_muted != should_mute { - if should_mute { - Audio::play_sound(Sound::Mute, cx); - } else { - Audio::play_sound(Sound::Unmute, cx); - } - } - - Ok((result, old_muted)) - } - fn stop_publishing(&mut self, cx: &mut ModelContext) { if let LocalTrack::Published { track_publication, .. @@ -1663,11 +1656,9 @@ enum LocalTrack { None, Pending { publish_id: usize, - muted: bool, }, Published { track_publication: LocalTrackPublication, - muted: bool, }, } diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 03dfd450704153b31c52ec3d0baad0d215389190..1485bb996aa9825f32c84032272ca720a2ec91dd 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -102,7 +102,7 @@ impl Render for CollabTitlebarItem { peer_id, true, room.is_speaking(), - room.is_muted(cx), + room.is_muted(), &room, project_id, ¤t_user, @@ -168,7 +168,7 @@ impl Render for CollabTitlebarItem { let project = self.project.read(cx); let is_local = project.is_local(); let is_shared = is_local && project.is_shared(); - let is_muted = room.is_muted(cx); + let is_muted = room.is_muted(); let is_deafened = room.is_deafened().unwrap_or(false); let is_screen_sharing = room.is_screen_sharing(); let read_only = room.read_only(); diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index c8230620b4c7756dbfee2b26011c32634f3b005a..a4faffdff2391381bd96cd1fa319b2bbccfa1a96 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -9,7 +9,7 @@ mod panel_settings; use std::{rc::Rc, sync::Arc}; -use call::{report_call_event_for_room, ActiveCall, Room}; +use call::{report_call_event_for_room, ActiveCall}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; @@ -21,7 +21,6 @@ pub use panel_settings::{ ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings, }; use settings::Settings; -use util::ResultExt; use workspace::AppState; actions!( @@ -79,7 +78,7 @@ pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { if let Some(room) = call.room().cloned() { let client = call.client(); room.update(cx, |room, cx| { - let operation = if room.is_muted(cx) { + let operation = if room.is_muted() { "enable microphone" } else { "disable microphone" @@ -87,17 +86,13 @@ pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { report_call_event_for_room(operation, room.id(), room.channel_id(), &client); room.toggle_mute(cx) - }) - .map(|task| task.detach_and_log_err(cx)) - .log_err(); + }); } } pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) { if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { - room.update(cx, Room::toggle_deafen) - .map(|task| task.detach_and_log_err(cx)) - .log_err(); + room.update(cx, |room, cx| room.toggle_deafen(cx)); } } From 51218811cff497b99668b7bef95da0ac9aa2f221 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Jan 2024 17:29:33 -0800 Subject: [PATCH 167/334] Don't initialize audio crate in integration tests Otherwise, a lot of time is spent in Audio::play_sound --- crates/collab/src/tests/test_server.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 4fcf6aa67652fabe9187ba5f78b8899379dc471b..cda0621cb32385a399fdfdaef51821dd531281b2 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -248,7 +248,6 @@ impl TestServer { language::init(cx); editor::init(cx); workspace::init(app_state.clone(), cx); - audio::init((), cx); call::init(client.clone(), user_store.clone(), cx); channel::init(&client, user_store.clone(), cx); notifications::init(client.clone(), user_store, cx); From 3a836b80263b6c05957ac1c625323701e22a0913 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 11 Jan 2024 10:28:22 -0800 Subject: [PATCH 168/334] Remove some comments --- crates/collab_ui/src/collab_ui.rs | 32 ----------------- crates/editor/src/editor.rs | 35 ++++++++----------- crates/gpui/src/color.rs | 10 ------ crates/gpui/src/taffy.rs | 14 -------- crates/gpui/src/view.rs | 10 ------ crates/gpui_macros/src/register_action.rs | 13 ------- .../src/derive_refineable.rs | 11 ------ 7 files changed, 15 insertions(+), 110 deletions(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index c8230620b4c7756dbfee2b26011c32634f3b005a..779fd121f8afbfa59eac556c17e7abba605e8eee 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -41,10 +41,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { chat_panel::init(cx); notification_panel::init(cx); notifications::init(&app_state, cx); - - // cx.add_global_action(toggle_screen_sharing); - // cx.add_global_action(toggle_mute); - // cx.add_global_action(toggle_deafen); } pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { @@ -131,34 +127,6 @@ fn notification_window_options( } } -// fn render_avatar( -// avatar: Option>, -// avatar_style: &AvatarStyle, -// container: ContainerStyle, -// ) -> AnyElement { -// avatar -// .map(|avatar| { -// Image::from_data(avatar) -// .with_style(avatar_style.image) -// .aligned() -// .contained() -// .with_corner_radius(avatar_style.outer_corner_radius) -// .constrained() -// .with_width(avatar_style.outer_width) -// .with_height(avatar_style.outer_width) -// .into_any() -// }) -// .unwrap_or_else(|| { -// Empty::new() -// .constrained() -// .with_width(avatar_style.outer_width) -// .into_any() -// }) -// .contained() -// .with_style(container) -// .into_any() -// } - fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { cx.is_staff() || cx.has_flag::() } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 66d30b049cb396651368d77c345c7b50e5c6554d..0bfd6afa3b265dfb3845e944b6d7a916ca066261 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1955,17 +1955,21 @@ impl Editor { } } - // pub fn language_at<'a, T: ToOffset>( - // &self, - // point: T, - // cx: &'a AppContext, - // ) -> Option> { - // self.buffer.read(cx).language_at(point, cx) - // } - - // pub fn file_at<'a, T: ToOffset>(&self, point: T, cx: &'a AppContext) -> Option> { - // self.buffer.read(cx).read(cx).file_at(point).cloned() - // } + pub fn language_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> Option> { + self.buffer.read(cx).language_at(point, cx) + } + + pub fn file_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> Option> { + self.buffer.read(cx).read(cx).file_at(point).cloned() + } pub fn active_excerpt( &self, @@ -1976,15 +1980,6 @@ impl Editor { .excerpt_containing(self.selections.newest_anchor().head(), cx) } - // pub fn style(&self, cx: &AppContext) -> EditorStyle { - // build_style( - // settings::get::(cx), - // self.get_field_editor_theme.as_deref(), - // self.override_text_style.as_deref(), - // cx, - // ) - // } - pub fn mode(&self) -> EditorMode { self.mode } diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index bc764e564c3340957f947ef669243be0a7d27e4d..23fcc25f6aeed436309a3670393d4cbca10536e6 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -355,16 +355,6 @@ impl Hsla { } } -// impl From for Rgba { -// fn from(value: Hsla) -> Self { -// let h = value.h; -// let s = value.s; -// let l = value.l; - -// let c = (1 - |2L - 1|) X s -// } -// } - impl From for Hsla { fn from(color: Rgba) -> Self { let r = color.r; diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 0ebd394217ae06c6a2256281f389ab506bfca934..cf8cb9ec327d66176ced0d6aaa51bb71a91c20b8 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -271,20 +271,6 @@ impl ToTaffy for Style { } } -// impl ToTaffy for Bounds { -// type Output = taffy::prelude::Bounds; - -// fn to_taffy( -// &self, -// rem_size: Pixels, -// ) -> taffy::prelude::Bounds { -// taffy::prelude::Bounds { -// origin: self.origin.to_taffy(rem_size), -// size: self.size.to_taffy(rem_size), -// } -// } -// } - impl ToTaffy for Length { fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto { match self { diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 4472da02e71fda1bb17d4353056b67ad58639813..247a5649967bc8cc1ca0c0a7ff369e78912bdd03 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -60,16 +60,6 @@ impl View { self.model.read(cx) } - // pub fn render_with(&self, component: E) -> RenderViewWith - // where - // E: 'static + Element, - // { - // RenderViewWith { - // view: self.clone(), - // element: Some(component), - // } - // } - pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle where V: FocusableView, diff --git a/crates/gpui_macros/src/register_action.rs b/crates/gpui_macros/src/register_action.rs index c18e4f4b89a68859b1c413357649cf6ad025e8d5..2772ec963485852e570ee27a8e7e3381dcd465d2 100644 --- a/crates/gpui_macros/src/register_action.rs +++ b/crates/gpui_macros/src/register_action.rs @@ -1,16 +1,3 @@ -// Input: -// -// struct FooBar {} - -// Output: -// -// struct FooBar {} -// -// #[allow(non_snake_case)] -// #[gpui2::ctor] -// fn register_foobar_builder() { -// gpui2::register_action_builder::() -// } use proc_macro::TokenStream; use proc_macro2::Ident; use quote::{format_ident, quote}; diff --git a/crates/refineable/derive_refineable/src/derive_refineable.rs b/crates/refineable/derive_refineable/src/derive_refineable.rs index ad7678b58fe696f61c14776c316bb9d159044b2f..99418206462a0dc7bc3babd2f9bda534a69a0f39 100644 --- a/crates/refineable/derive_refineable/src/derive_refineable.rs +++ b/crates/refineable/derive_refineable/src/derive_refineable.rs @@ -69,13 +69,6 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { path: parse_quote!(Clone), })); - // punctuated.push_punct(syn::token::Add::default()); - // punctuated.push_value(TypeParamBound::Trait(TraitBound { - // paren_token: None, - // modifier: syn::TraitBoundModifier::None, - // lifetimes: None, - // path: parse_quote!(Default), - // })); punctuated }, }) @@ -94,10 +87,6 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { }, }; - // refinable_refine_assignments - // refinable_refined_assignments - // refinement_refine_assignments - let refineable_refine_assignments: Vec = fields .iter() .map(|field| { From 5897b18cfd353bebb9a53a4add4530766b8df2b2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 11:42:03 -0800 Subject: [PATCH 169/334] remove more commented code --- crates/collab/src/tests/channel_tests.rs | 2 - crates/collab_ui/src/collab_panel.rs | 9 -- crates/gpui/src/style.rs | 104 +++++++++--------- crates/gpui/src/window.rs | 7 -- crates/project/src/project.rs | 14 --- crates/project_panel/src/project_panel.rs | 32 +++--- crates/rich_text/src/rich_text.rs | 33 +----- crates/rpc/build.rs | 1 - crates/search/src/buffer_search.rs | 1 - .../src/semantic_index_tests.rs | 2 - 10 files changed, 67 insertions(+), 138 deletions(-) diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 2a88bc4c579db9855184e278d1fef37200d1f470..e80fe0fdca312bed54d98fce0a1ea69a8a4a6e86 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1418,8 +1418,6 @@ async fn test_channel_moving( ) { let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; - // let client_b = server.create_client(cx_b, "user_b").await; - // let client_c = server.create_client(cx_c, "user_c").await; let channels = server .make_channel_tree( diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5ad3d6cfa3213119551ed341be33816336f8ca5c..13b378a341b419ce66a7685e11490e46f850f6b1 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1426,14 +1426,6 @@ impl CollabPanel { self.toggle_channel_collapsed(id, cx) } - // fn toggle_channel_collapsed_action( - // &mut self, - // action: &ToggleCollapse, - // cx: &mut ViewContext, - // ) { - // self.toggle_channel_collapsed(action.location, cx); - // } - fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { match self.collapsed_channels.binary_search(&channel_id) { Ok(ix) => { @@ -1910,7 +1902,6 @@ impl CollabPanel { let mut channel_link = None; let mut channel_tooltip_text = None; let mut channel_icon = None; - // let mut is_dragged_over = false; let text = match section { Section::ActiveCall => { diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index a21957611d09feb25f59d4a842ab9b998e83b4d4..32b749c257b0def7913c835141ce25801dcf4afd 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -1,10 +1,10 @@ use std::{iter, mem, ops::Range}; use crate::{ - black, phi, point, quad, rems, AbsoluteLength, BorrowWindow, Bounds, ContentMask, Corners, - CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, - FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size, - SizeRefinement, Styled, TextRun, WindowContext, + black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, + ContentMask, Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, + Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, + SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext, }; use collections::HashSet; use refineable::{Cascade, Refineable}; @@ -308,54 +308,54 @@ impl Style { } } - // pub fn apply_text_style(&self, cx: &mut C, f: F) -> R - // where - // C: BorrowAppContext, - // F: FnOnce(&mut C) -> R, - // { - // if self.text.is_some() { - // cx.with_text_style(Some(self.text.clone()), f) - // } else { - // f(cx) - // } - // } - - // /// Apply overflow to content mask - // pub fn apply_overflow(&self, bounds: Bounds, cx: &mut C, f: F) -> R - // where - // C: BorrowWindow, - // F: FnOnce(&mut C) -> R, - // { - // let current_mask = cx.content_mask(); - - // let min = current_mask.bounds.origin; - // let max = current_mask.bounds.lower_right(); - - // let mask_bounds = match ( - // self.overflow.x == Overflow::Visible, - // self.overflow.y == Overflow::Visible, - // ) { - // // x and y both visible - // (true, true) => return f(cx), - // // x visible, y hidden - // (true, false) => Bounds::from_corners( - // point(min.x, bounds.origin.y), - // point(max.x, bounds.lower_right().y), - // ), - // // x hidden, y visible - // (false, true) => Bounds::from_corners( - // point(bounds.origin.x, min.y), - // point(bounds.lower_right().x, max.y), - // ), - // // both hidden - // (false, false) => bounds, - // }; - // let mask = ContentMask { - // bounds: mask_bounds, - // }; - - // cx.with_content_mask(Some(mask), f) - // } + pub fn apply_text_style(&self, cx: &mut C, f: F) -> R + where + C: BorrowAppContext, + F: FnOnce(&mut C) -> R, + { + if self.text.is_some() { + cx.with_text_style(Some(self.text.clone()), f) + } else { + f(cx) + } + } + + /// Apply overflow to content mask + pub fn apply_overflow(&self, bounds: Bounds, cx: &mut C, f: F) -> R + where + C: BorrowWindow, + F: FnOnce(&mut C) -> R, + { + let current_mask = cx.content_mask(); + + let min = current_mask.bounds.origin; + let max = current_mask.bounds.lower_right(); + + let mask_bounds = match ( + self.overflow.x == Overflow::Visible, + self.overflow.y == Overflow::Visible, + ) { + // x and y both visible + (true, true) => return f(cx), + // x visible, y hidden + (true, false) => Bounds::from_corners( + point(min.x, bounds.origin.y), + point(max.x, bounds.lower_right().y), + ), + // x hidden, y visible + (false, true) => Bounds::from_corners( + point(bounds.origin.x, min.y), + point(bounds.lower_right().x, max.y), + ), + // both hidden + (false, false) => bounds, + }; + let mask = ContentMask { + bounds: mask_bounds, + }; + + cx.with_content_mask(Some(mask), f) + } /// Paints the background of an element styled with this style. pub fn paint( diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 509a6d8466609b5041f4f958ca1676107b639a14..470f076d78c4a06a2d3b0856974a8ce0bf8a7612 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3137,13 +3137,6 @@ impl AnyWindowHandle { } } -// #[cfg(any(test, feature = "test-support"))] -// impl From> for StackingOrder { -// fn from(small_vec: SmallVec<[u32; 16]>) -> Self { -// StackingOrder(small_vec) -// } -// } - /// An identifier for an [`Element`](crate::Element). /// /// Can be constructed with a string, a number, or both, as well diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 06b6da75b3ed382d03becbb5a418ddf28cbc05c0..5f37bbfce6483e359866a0dadb1d63b2e32b4651 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -99,20 +99,6 @@ pub trait Item { fn project_path(&self, cx: &AppContext) -> Option; } -// Language server state is stored across 3 collections: -// language_servers => -// a mapping from unique server id to LanguageServerState which can either be a task for a -// server in the process of starting, or a running server with adapter and language server arcs -// language_server_ids => a mapping from worktreeId and server name to the unique server id -// language_server_statuses => a mapping from unique server id to the current server status -// -// Multiple worktrees can map to the same language server for example when you jump to the definition -// of a file in the standard library. So language_server_ids is used to look up which server is active -// for a given worktree and language server name -// -// When starting a language server, first the id map is checked to make sure a server isn't already available -// for that worktree. If there is one, it finishes early. Otherwise, a new id is allocated and and -// the Starting variant of LanguageServerState is stored in the language_servers map. pub struct Project { worktrees: Vec, active_entry: Option, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 251e26ebfba004b81a49c1ce28956e01f42bbce5..ef48cd683207db2a8e51790dc5026133c68cb2df 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -221,10 +221,10 @@ impl ProjectPanel { }) .detach(); - // cx.observe_global::(|_, cx| { - // cx.notify(); - // }) - // .detach(); + cx.observe_global::(|_, cx| { + cx.notify(); + }) + .detach(); let mut this = Self { project: project.clone(), @@ -292,16 +292,16 @@ impl ProjectPanel { } &Event::SplitEntry { entry_id } => { if let Some(worktree) = project.read(cx).worktree_for_entry(entry_id, cx) { - if let Some(_entry) = worktree.read(cx).entry_for_id(entry_id) { - // workspace - // .split_path( - // ProjectPath { - // worktree_id: worktree.read(cx).id(), - // path: entry.path.clone(), - // }, - // cx, - // ) - // .detach_and_log_err(cx); + if let Some(entry) = worktree.read(cx).entry_for_id(entry_id) { + workspace + .split_path( + ProjectPath { + worktree_id: worktree.read(cx).id(), + path: entry.path.clone(), + }, + cx, + ) + .detach_and_log_err(cx); } } } @@ -788,10 +788,6 @@ impl ProjectPanel { cx.notify(); } } - - // cx.update_global(|drag_and_drop: &mut DragAndDrop, cx| { - // drag_and_drop.cancel_dragging::(cx); - // }) } } diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index b4a87b1e5de3ed3c71af8c1dcaf9a3ab7a2e4e96..83dd007308721f38cf9305f0b9739f0901fc2942 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -1,3 +1,4 @@ +use anyhow::bail; use futures::FutureExt; use gpui::{ AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement, @@ -85,31 +86,6 @@ impl RichText { }) .into_any_element() } - - // pub fn add_mention( - // &mut self, - // range: Range, - // is_current_user: bool, - // mention_style: HighlightStyle, - // ) -> anyhow::Result<()> { - // if range.end > self.text.len() { - // bail!( - // "Mention in range {range:?} is outside of bounds for a message of length {}", - // self.text.len() - // ); - // } - - // if is_current_user { - // self.region_ranges.push(range.clone()); - // self.regions.push(RenderedRegion { - // background_kind: Some(BackgroundKind::Mention), - // link_url: None, - // }); - // } - // self.highlights - // .push((range, Highlight::Highlight(mention_style))); - // Ok(()) - // } } pub fn render_markdown_mut( @@ -272,13 +248,6 @@ pub fn render_markdown( language_registry: &Arc, language: Option<&Arc>, ) -> RichText { - // let mut data = RichText { - // text: Default::default(), - // highlights: Default::default(), - // region_ranges: Default::default(), - // regions: Default::default(), - // }; - let mut text = String::new(); let mut highlights = Vec::new(); let mut link_ranges = Vec::new(); diff --git a/crates/rpc/build.rs b/crates/rpc/build.rs index 66b289f1db83ab47d9ffaeaff8ec172838b4921f..25dff9b007c148c25aab0f4bd87e07bcb543d08a 100644 --- a/crates/rpc/build.rs +++ b/crates/rpc/build.rs @@ -1,6 +1,5 @@ fn main() { let mut build = prost_build::Config::new(); - // build.protoc_arg("--experimental_allow_proto3_optional"); build .type_attribute(".", "#[derive(serde::Serialize)]") .compile_protos(&["proto/zed.proto"], &["proto"]) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index f7e36fe696258fa1a361bc6bd212f1d888efc2f8..9cbe49d99ea65414a89649be17fb3c5dc196cd83 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1648,7 +1648,6 @@ mod tests { #[gpui::test] async fn test_search_query_history(cx: &mut TestAppContext) { - //crate::project_search::tests::init_test(cx); init_globals(cx); let buffer_text = r#" A regular expression (shortened as regex or regexp;[1] also referred to as diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index ced08f4cbc30a991bfad0577af24f96c8ff81d8b..e340b44a58377b8a9bda52786dea660637ce54c1 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -1677,8 +1677,6 @@ fn elixir_lang() -> Arc { #[gpui::test] fn test_subtract_ranges() { - // collapsed_ranges: Vec>, keep_ranges: Vec> - assert_eq!( subtract_ranges(&[0..5, 10..21], &[0..1, 4..5]), vec![1..4, 10..21] From bfb59f1598e8e2497fd7e635c51340c7a40a07f1 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 20:59:19 -0800 Subject: [PATCH 170/334] Remove last stale code --- crates/collab_ui/src/collab_panel.rs | 2 +- crates/storybook/src/storybook.rs | 5 - .../ui/src/components/stories/icon_button.rs | 50 ------- crates/workspace/src/item.rs | 21 --- crates/workspace/src/pane.rs | 137 ------------------ crates/workspace/src/persistence.rs | 2 - crates/workspace/src/workspace.rs | 30 ---- crates/zed/src/languages/python.rs | 2 +- crates/zed/src/zed.rs | 6 - 9 files changed, 2 insertions(+), 253 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 13b378a341b419ce66a7685e11490e46f850f6b1..ca54aa49c80f134da3f17c51ba124324f8845c69 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2043,7 +2043,7 @@ impl CollabPanel { }), ) .start_slot( - // todo!() handle contacts with no avatar + // todo handle contacts with no avatar Avatar::new(contact.user.avatar_uri.clone()) .availability_indicator(if online { Some(!busy) } else { None }), ) diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 8d60e29a136013a8e38e33f8ebb5ce8044d4b2d6..ceab82e12b02d0080c2374517317bb2401e1556b 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -21,11 +21,6 @@ use crate::assets::Assets; use crate::story_selector::{ComponentStory, StorySelector}; pub use indoc::indoc; -// gpui::actions! { -// storybook, -// [ToggleInspector] -// } - #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Args { diff --git a/crates/ui/src/components/stories/icon_button.rs b/crates/ui/src/components/stories/icon_button.rs index 6a67183e97c73b3795fc14a71c11a16b6012f549..df9f37b164782f35c9a2ca1cfba6aa96d8783d60 100644 --- a/crates/ui/src/components/stories/icon_button.rs +++ b/crates/ui/src/components/stories/icon_button.rs @@ -117,55 +117,5 @@ impl Render for IconButtonStory { ) .children(vec![StorySection::new().children(buttons)]) .into_element() - - // Story::container() - // .child(Story::title_for::()) - // .child(Story::label("Default")) - // .child(div().w_8().child(IconButton::new("icon_a", Icon::Hash))) - // .child(Story::label("Selected")) - // .child( - // div() - // .w_8() - // .child(IconButton::new("icon_a", Icon::Hash).selected(true)), - // ) - // .child(Story::label("Selected with `selected_icon`")) - // .child( - // div().w_8().child( - // IconButton::new("icon_a", Icon::AudioOn) - // .selected(true) - // .selected_icon(Icon::AudioOff), - // ), - // ) - // .child(Story::label("Disabled")) - // .child( - // div() - // .w_8() - // .child(IconButton::new("icon_a", Icon::Hash).disabled(true)), - // ) - // .child(Story::label("With `on_click`")) - // .child( - // div() - // .w_8() - // .child( - // IconButton::new("with_on_click", Icon::Ai).on_click(|_event, _cx| { - // println!("Clicked!"); - // }), - // ), - // ) - // .child(Story::label("With `tooltip`")) - // .child( - // div().w_8().child( - // IconButton::new("with_tooltip", Icon::MessageBubbles) - // .tooltip(|cx| Tooltip::text("Open messages", cx)), - // ), - // ) - // .child(Story::label("Selected with `tooltip`")) - // .child( - // div().w_8().child( - // IconButton::new("selected_with_tooltip", Icon::InlayHint) - // .selected(true) - // .tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)), - // ), - // ) } } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index c629edc696b87e0552ca05956e2a1c5cf3b5e6b0..fb4ed05f6c006d8118304418a76dd6dcffc67576 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -809,27 +809,6 @@ pub mod test { Edit, } - // impl Clone for TestItem { - // fn clone(&self) -> Self { - // Self { - // state: self.state.clone(), - // label: self.label.clone(), - // save_count: self.save_count, - // save_as_count: self.save_as_count, - // reload_count: self.reload_count, - // is_dirty: self.is_dirty, - // is_singleton: self.is_singleton, - // has_conflict: self.has_conflict, - // project_items: self.project_items.clone(), - // nav_history: None, - // tab_descriptions: None, - // tab_detail: Default::default(), - // workspace_id: self.workspace_id, - // focus_handle: self.focus_handle.clone(), - // } - // } - // } - impl TestProjectItem { pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model { let entry_id = Some(ProjectEntryId::from_proto(id)); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index aec33c2dd32ee9bda77e7fe7927e1346a86178df..1b95671398a613a01f41590bdce20e0a846592d0 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -60,24 +60,6 @@ pub enum SaveIntent { #[derive(Clone, Deserialize, PartialEq, Debug)] pub struct ActivateItem(pub usize); -// #[derive(Clone, PartialEq)] -// pub struct CloseItemById { -// pub item_id: usize, -// pub pane: WeakView, -// } - -// #[derive(Clone, PartialEq)] -// pub struct CloseItemsToTheLeftById { -// pub item_id: usize, -// pub pane: WeakView, -// } - -// #[derive(Clone, PartialEq)] -// pub struct CloseItemsToTheRightById { -// pub item_id: usize, -// pub pane: WeakView, -// } - #[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct CloseActiveItem { @@ -1226,125 +1208,6 @@ impl Pane { cx.emit(Event::Split(direction)); } - // fn deploy_split_menu(&mut self, cx: &mut ViewContext) { - // self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - // menu.toggle( - // Default::default(), - // AnchorCorner::TopRight, - // vec![ - // ContextMenuItem::action("Split Right", SplitRight), - // ContextMenuItem::action("Split Left", SplitLeft), - // ContextMenuItem::action("Split Up", SplitUp), - // ContextMenuItem::action("Split Down", SplitDown), - // ], - // cx, - // ); - // }); - - // self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split; - // } - - // fn deploy_new_menu(&mut self, cx: &mut ViewContext) { - // self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - // menu.toggle( - // Default::default(), - // AnchorCorner::TopRight, - // vec![ - // ContextMenuItem::action("New File", NewFile), - // ContextMenuItem::action("New Terminal", NewCenterTerminal), - // ContextMenuItem::action("New Search", NewSearch), - // ], - // cx, - // ); - // }); - - // self.tab_bar_context_menu.kind = TabBarContextMenuKind::New; - // } - - // fn deploy_tab_context_menu( - // &mut self, - // position: Vector2F, - // target_item_id: usize, - // cx: &mut ViewContext, - // ) { - // let active_item_id = self.items[self.active_item_index].id(); - // let is_active_item = target_item_id == active_item_id; - // let target_pane = cx.weak_handle(); - - // // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab - - // self.tab_context_menu.update(cx, |menu, cx| { - // menu.show( - // position, - // AnchorCorner::TopLeft, - // if is_active_item { - // vec![ - // ContextMenuItem::action( - // "Close Active Item", - // CloseActiveItem { save_intent: None }, - // ), - // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - // ContextMenuItem::action("Close Clean Items", CloseCleanItems), - // ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft), - // ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight), - // ContextMenuItem::action( - // "Close All Items", - // CloseAllItems { save_intent: None }, - // ), - // ] - // } else { - // // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command. - // vec![ - // ContextMenuItem::handler("Close Inactive Item", { - // let pane = target_pane.clone(); - // move |cx| { - // if let Some(pane) = pane.upgrade(cx) { - // pane.update(cx, |pane, cx| { - // pane.close_item_by_id( - // target_item_id, - // SaveIntent::Close, - // cx, - // ) - // .detach_and_log_err(cx); - // }) - // } - // } - // }), - // ContextMenuItem::action("Close Inactive Items", CloseInactiveItems), - // ContextMenuItem::action("Close Clean Items", CloseCleanItems), - // ContextMenuItem::handler("Close Items To The Left", { - // let pane = target_pane.clone(); - // move |cx| { - // if let Some(pane) = pane.upgrade(cx) { - // pane.update(cx, |pane, cx| { - // pane.close_items_to_the_left_by_id(target_item_id, cx) - // .detach_and_log_err(cx); - // }) - // } - // } - // }), - // ContextMenuItem::handler("Close Items To The Right", { - // let pane = target_pane.clone(); - // move |cx| { - // if let Some(pane) = pane.upgrade(cx) { - // pane.update(cx, |pane, cx| { - // pane.close_items_to_the_right_by_id(target_item_id, cx) - // .detach_and_log_err(cx); - // }) - // } - // } - // }), - // ContextMenuItem::action( - // "Close All Items", - // CloseAllItems { save_intent: None }, - // ), - // ] - // }, - // cx, - // ); - // }); - // } - pub fn toolbar(&self) -> &View { &self.toolbar } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d03c7b3d0f73c21bde176d416060ccc200aaa62e..56aa6e4322bce652aacda0ddb1f0c77cef61b7ee 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -1,5 +1,3 @@ -//#![allow(dead_code)] - pub mod model; use std::path::Path; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ca463e76e058c645839f853eadd8e0877ecca6da..efd2c52989edb51cff2559382f0ec62a2ce2702e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3324,36 +3324,6 @@ impl Workspace { workspace } - // fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { - // let dock = match position { - // DockPosition::Left => &self.left_dock, - // DockPosition::Right => &self.right_dock, - // DockPosition::Bottom => &self.bottom_dock, - // }; - // let active_panel = dock.read(cx).visible_panel()?; - // let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { - // dock.read(cx).render_placeholder(cx) - // } else { - // ChildView::new(dock, cx).into_any() - // }; - - // Some( - // element - // .constrained() - // .dynamically(move |constraint, _, cx| match position { - // DockPosition::Left | DockPosition::Right => SizeConstraint::new( - // Vector2F::new(20., constraint.min.y()), - // Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), - // ), - // DockPosition::Bottom => SizeConstraint::new( - // Vector2F::new(constraint.min.x(), 20.), - // Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8), - // ), - // }) - // .into_any(), - // ) - // } - // } pub fn register_action( &mut self, callback: impl Fn(&mut Self, &A, &mut ViewContext) + 'static, diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index d28cd9f6e410cec04b3a3081e65166fea80ec159..8a30121d49c2ba350a42e5352065e1b7f886f0c7 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -184,7 +184,7 @@ mod tests { #[gpui::test] async fn test_python_autoindent(cx: &mut TestAppContext) { - // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX); + cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX); let language = crate::languages::language("python", tree_sitter_python::language(), None).await; cx.update(|cx| { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c2725eef64029a11cbf449769ee5f025ae7b0535..d7686c425ad6a40663aeb6552e790d00aab50f77 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -113,12 +113,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }) .detach(); - // cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); - - // let collab_titlebar_item = - // cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx)); - // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx); - let copilot = cx.new_view(|cx| copilot_ui::CopilotButton::new(app_state.fs.clone(), cx)); let diagnostic_summary = cx.new_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx)); From bb35805f9bf0dcd9d505e26613b3ffa1f95b3a4b Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 21:02:01 -0800 Subject: [PATCH 171/334] fmt --- crates/rich_text/src/rich_text.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index 83dd007308721f38cf9305f0b9739f0901fc2942..771f5602369093454b8252bd76dacf5e5002a5f8 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -1,4 +1,3 @@ -use anyhow::bail; use futures::FutureExt; use gpui::{ AnyElement, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement, From 78858d4d11de009a3c54beea87d8c3022707ec1d Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 21:11:17 -0800 Subject: [PATCH 172/334] Disable searches for '.', so that users with large monitors don't accidentally crash the terminal when searching for dot files. --- crates/terminal_view/src/terminal_view.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index ced122402f138e5f5b964792d7a1561260b063b6..98a04eb5f5598edab5010125da60d6a171994422 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -600,6 +600,9 @@ fn possible_open_targets( pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option { let query = query.as_str(); + if query == "." { + return None; + } let searcher = RegexSearch::new(&query); searcher.ok() } From 1d7dc96135fcf5b7dea358aeaa6d23f52d9eee9f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 21:37:09 -0800 Subject: [PATCH 173/334] Restore temp file initialization in telemetry code --- crates/client/src/telemetry.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index aa0c7c4af58be6c181a04e2b73cff55230096bf9..ee001614400def4fe40f1d5328027704e76b0141 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -14,6 +14,7 @@ use sysinfo::{ }; use tempfile::NamedTempFile; use util::http::HttpClient; +use util::ResultExt; use util::{channel::ReleaseChannel, TryFutureExt}; use self::event_coalescer::EventCoalescer; @@ -167,6 +168,19 @@ impl Telemetry { event_coalescer: EventCoalescer::new(), })); + cx.background_executor() + .spawn({ + let state = state.clone(); + async move { + if let Some(tempfile) = + NamedTempFile::new_in(util::paths::CONFIG_DIR.as_path()).log_err() + { + state.lock().log_file = Some(tempfile); + } + } + }) + .detach(); + cx.observe_global::({ let state = state.clone(); From 4d6dfa319d5d85211a71d0a0837d94732f0bcaff Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 22:44:07 -0800 Subject: [PATCH 174/334] Don't open files unescessary in dev builds --- crates/client/src/telemetry.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ee001614400def4fe40f1d5328027704e76b0141..ca717c9d6a6463b0351c0aa312f8aeeefab8c8ac 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -14,6 +14,7 @@ use sysinfo::{ }; use tempfile::NamedTempFile; use util::http::HttpClient; +#[cfg(not(debug_assertions))] use util::ResultExt; use util::{channel::ReleaseChannel, TryFutureExt}; @@ -168,6 +169,7 @@ impl Telemetry { event_coalescer: EventCoalescer::new(), })); + #[cfg(not(debug_assertions))] cx.background_executor() .spawn({ let state = state.clone(); From c2ff9fe2da012be54a78b3d335bb3983aeb424d2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 14:32:24 -0700 Subject: [PATCH 175/334] Don't lose focus on default panel state --- crates/collab_ui/src/chat_panel.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index ecd7d1a739e957bfa7c7bf7b19de774e97675e64..20c5857ad2e07d84c03f54793571851e5783bac5 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -59,6 +59,7 @@ pub struct ChatPanel { subscriptions: Vec, is_scrolled_to_bottom: bool, markdown_data: HashMap, + focus_handle: FocusHandle, } #[derive(Serialize, Deserialize)] @@ -126,6 +127,7 @@ impl ChatPanel { active: false, width: None, markdown_data: Default::default(), + focus_handle: cx.focus_handle(), }; let mut old_dock_position = this.position(cx); @@ -490,6 +492,7 @@ impl EventEmitter for ChatPanel {} impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_stack() + .track_focus(&self.focus_handle) .full() .on_action(cx.listener(Self::send)) .child( @@ -559,7 +562,11 @@ impl Render for ChatPanel { impl FocusableView for ChatPanel { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { - self.message_editor.read(cx).focus_handle(cx) + if self.active_chat.is_some() { + self.message_editor.read(cx).focus_handle(cx) + } else { + self.focus_handle.clone() + } } } From f6ef07e7166eadbb4e65de941e4c5422162319d5 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 21:37:13 -0700 Subject: [PATCH 176/334] Make chat prettier (to my eyes at least) --- crates/channel/src/channel_chat.rs | 4 +- crates/collab_ui/src/chat_panel.rs | 106 ++++++++++++++++++++++------- crates/gpui/src/styled.rs | 11 ++- crates/ui/src/components/avatar.rs | 10 ++- 4 files changed, 101 insertions(+), 30 deletions(-) diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index d2250972f3095893458a873980d26ceb13240cf3..e9353a14419adf069a6319ed76997754008caa18 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -144,7 +144,7 @@ impl ChannelChat { message: MessageParams, cx: &mut ModelContext, ) -> Result>> { - if message.text.is_empty() { + if message.text.trim().is_empty() { Err(anyhow!("message body can't be empty"))?; } @@ -174,6 +174,8 @@ impl ChannelChat { let user_store = self.user_store.clone(); let rpc = self.rpc.clone(); let outgoing_messages_lock = self.outgoing_messages_lock.clone(); + + // todo - handle messages that fail to send (e.g. >1024 chars) Ok(cx.spawn(move |this, mut cx| async move { let outgoing_message_guard = outgoing_messages_lock.lock().await; let request = rpc.request(proto::SendChannelMessage { diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 20c5857ad2e07d84c03f54793571851e5783bac5..dd3d7415225b4aae56bd8927c6884981a6681c74 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -8,8 +8,9 @@ use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ actions, div, list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, - ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView, ListOffset, ListScrollEvent, - ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView, + ClickEvent, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, + ListOffset, ListScrollEvent, ListState, Model, Render, Subscription, Task, View, ViewContext, + VisualContext, WeakView, }; use language::LanguageRegistry; use menu::Confirm; @@ -22,7 +23,8 @@ use settings::{Settings, SettingsStore}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; use ui::{ - prelude::*, Avatar, Button, IconButton, IconName, Key, KeyBinding, Label, TabBar, Tooltip, + popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, Key, KeyBinding, + Label, TabBar, Tooltip, }; use util::{ResultExt, TryFutureExt}; use workspace::{ @@ -60,6 +62,7 @@ pub struct ChatPanel { is_scrolled_to_bottom: bool, markdown_data: HashMap, focus_handle: FocusHandle, + open_context_menu: Option<(u64, Subscription)>, } #[derive(Serialize, Deserialize)] @@ -128,6 +131,7 @@ impl ChatPanel { width: None, markdown_data: Default::default(), focus_handle: cx.focus_handle(), + open_context_menu: None, }; let mut old_dock_position = this.position(cx); @@ -348,50 +352,100 @@ impl ChatPanel { ChannelMessageId::Saved(id) => ("saved-message", id).into(), ChannelMessageId::Pending(id) => ("pending-message", id).into(), }; + let this = cx.view().clone(); v_stack() .w_full() - .id(element_id) .relative() .overflow_hidden() - .group("") .when(!is_continuation_from_previous, |this| { - this.child( + this.pt_3().child( h_stack() - .gap_2() - .child(Avatar::new(message.sender.avatar_uri.clone())) - .child(Label::new(message.sender.github_login.clone())) + .child( + div().absolute().child( + Avatar::new(message.sender.avatar_uri.clone()) + .size(cx.rem_size() * 1.5), + ), + ) + .child( + div() + .pl(cx.rem_size() * 1.5 + px(6.0)) + .pr(px(8.0)) + .font_weight(FontWeight::BOLD) + .child(Label::new(message.sender.github_login.clone())), + ) .child( Label::new(format_timestamp( message.timestamp, now, self.local_timezone, )) + .size(LabelSize::Small) .color(Color::Muted), ), ) }) - .when(!is_continuation_to_next, |this| - // HACK: This should really be a margin, but margins seem to get collapsed. - this.pb_2()) - .child(text.element("body".into(), cx)) + .when(is_continuation_from_previous, |this| this.pt_1()) .child( - div() - .absolute() - .top_1() - .right_2() - .w_8() - .visible_on_hover("") - .children(message_id_to_remove.map(|message_id| { - IconButton::new(("remove", message_id), IconName::XCircle).on_click( - cx.listener(move |this, _, cx| { - this.remove_message(message_id, cx); - }), - ) - })), + v_stack() + .w_full() + .text_ui_sm() + .id(element_id) + .group("") + .child(text.element("body".into(), cx)) + .child( + div() + .absolute() + .z_index(1) + .right_0() + .w_6() + .bg(cx.theme().colors().panel_background) + .when(!self.has_open_menu(message_id_to_remove), |el| { + el.visible_on_hover("") + }) + .children(message_id_to_remove.map(|message_id| { + popover_menu(("menu", message_id)) + .trigger(IconButton::new( + ("trigger", message_id), + IconName::Ellipsis, + )) + .menu(move |cx| { + Some(Self::render_message_menu(&this, message_id, cx)) + }) + })), + ), ) } + fn has_open_menu(&self, message_id: Option) -> bool { + match self.open_context_menu.as_ref() { + Some((id, _)) => Some(*id) == message_id, + None => false, + } + } + + fn render_message_menu( + this: &View, + message_id: u64, + cx: &mut WindowContext, + ) -> View { + let menu = { + let this = this.clone(); + ContextMenu::build(cx, move |menu, _| { + menu.entry("Delete message", None, move |cx| { + this.update(cx, |this, cx| this.remove_message(message_id, cx)) + }) + }) + }; + this.update(cx, |this, cx| { + let subscription = cx.subscribe(&menu, |this: &mut Self, _, _: &DismissEvent, _| { + this.open_context_menu = None; + }); + this.open_context_menu = Some((message_id, subscription)); + }); + menu + } + fn render_markdown_with_mentions( language_registry: &Arc, current_user_id: u64, diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index 2749c31a788f8d1fd83fe1353aaae15179b859cb..0eba1771f52d47bde32f465a887e52547f3a89b2 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -1,7 +1,7 @@ use crate::{ self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, - DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, - SharedString, StyleRefinement, Visibility, WhiteSpace, + DefiniteLength, Display, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length, + Position, SharedString, StyleRefinement, Visibility, WhiteSpace, }; use crate::{BoxShadow, TextStyleRefinement}; use smallvec::{smallvec, SmallVec}; @@ -494,6 +494,13 @@ pub trait Styled: Sized { self } + fn font_weight(mut self, weight: FontWeight) -> Self { + self.text_style() + .get_or_insert_with(Default::default) + .font_weight = Some(weight); + self + } + fn text_bg(mut self, bg: impl Into) -> Self { self.text_style() .get_or_insert_with(Default::default) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 9e64e1223c346f4d153fb7d2955b606ff53736ae..a97adb73b7d88ae0dfb1c60c25eff3942c5ad52d 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -26,6 +26,7 @@ pub enum AvatarShape { #[derive(IntoElement)] pub struct Avatar { image: Img, + size: Option, border_color: Option, is_available: Option, } @@ -36,7 +37,7 @@ impl RenderOnce for Avatar { self = self.shape(AvatarShape::Circle); } - let size = cx.rem_size(); + let size = self.size.unwrap_or_else(|| cx.rem_size()); div() .size(size + px(2.)) @@ -78,6 +79,7 @@ impl Avatar { image: img(src), is_available: None, border_color: None, + size: None, } } @@ -124,4 +126,10 @@ impl Avatar { self.is_available = is_available.into(); self } + + /// Size overrides the avatar size. By default they are 1rem. + pub fn size(mut self, size: impl Into>) -> Self { + self.size = size.into(); + self + } } From c810af40d3a63ef7c82696101c6023e66b6e1185 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 21:53:22 -0700 Subject: [PATCH 177/334] Fix multiple mentions in one message --- crates/collab/src/db/queries/messages.rs | 1 + crates/rich_text/src/rich_text.rs | 28 +++++++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index 47bb27df39060a7fff27bd50218e9b3626d13dd6..96942c052df75c9406272da3a563533342f64406 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -256,6 +256,7 @@ impl Database { message_id = result.last_insert_id; let mentioned_user_ids = mentions.iter().map(|m| m.user_id).collect::>(); + let mentions = mentions .iter() .filter_map(|mention| { diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index b4a87b1e5de3ed3c71af8c1dcaf9a3ab7a2e4e96..ac3414555aeff0bec698e788da485517259c8ad1 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -39,6 +39,7 @@ pub struct RichText { /// Allows one to specify extra links to the rendered markdown, which can be used /// for e.g. mentions. +#[derive(Debug)] pub struct Mention { pub range: Range, pub is_self_mention: bool, @@ -138,20 +139,21 @@ pub fn render_markdown_mut( if let Some(language) = ¤t_language { render_code(text, highlights, t.as_ref(), language); } else { - if let Some(mention) = mentions.first() { - if source_range.contains_inclusive(&mention.range) { - mentions = &mentions[1..]; - let range = (prev_len + mention.range.start - source_range.start) - ..(prev_len + mention.range.end - source_range.start); - highlights.push(( - range.clone(), - if mention.is_self_mention { - Highlight::SelfMention - } else { - Highlight::Mention - }, - )); + while let Some(mention) = mentions.first() { + if !source_range.contains_inclusive(&mention.range) { + break; } + mentions = &mentions[1..]; + let range = (prev_len + mention.range.start - source_range.start) + ..(prev_len + mention.range.end - source_range.start); + highlights.push(( + range.clone(), + if mention.is_self_mention { + Highlight::SelfMention + } else { + Highlight::Mention + }, + )); } text.push_str(t.as_ref()); From 818cbb24157334adac4a448c237e0ab35019e41c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:19:21 -0700 Subject: [PATCH 178/334] Show a border when scrolled in chat --- crates/collab_ui/src/chat_panel.rs | 37 ++++++++++++++++++------------ crates/gpui/src/elements/list.rs | 2 ++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index dd3d7415225b4aae56bd8927c6884981a6681c74..ee0241eb1209dc6a36150c7d68b68250476c59e5 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -112,7 +112,7 @@ impl ChatPanel { if event.visible_range.start < MESSAGE_LOADING_THRESHOLD { this.load_more_messages(cx); } - this.is_scrolled_to_bottom = event.visible_range.end == event.count; + this.is_scrolled_to_bottom = !event.is_scrolled; })); let mut this = Self { @@ -567,7 +567,7 @@ impl Render for ChatPanel { ), ), ) - .child(div().flex_grow().px_2().py_1().map(|this| { + .child(div().flex_grow().px_2().pt_1().map(|this| { if self.active_chat.is_some() { this.child(list(self.message_list.clone()).full()) } else { @@ -597,19 +597,26 @@ impl Render for ChatPanel { ) } })) - .child(h_stack().p_2().map(|el| { - if self.active_chat.is_some() { - el.child(self.message_editor.clone()) - } else { - el.child( - div() - .rounded_md() - .h_7() - .w_full() - .bg(cx.theme().colors().editor_background), - ) - } - })) + .child( + h_stack() + .when(!self.is_scrolled_to_bottom, |el| { + el.border_t_1().border_color(cx.theme().colors().border) + }) + .p_2() + .map(|el| { + if self.active_chat.is_some() { + el.child(self.message_editor.clone()) + } else { + el.child( + div() + .rounded_md() + .h_7() + .w_full() + .bg(cx.theme().colors().editor_background), + ) + } + }), + ) .into_any() } } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 2a47a16741cf67c0cefb8a094d2f9e506cacbdf4..50e7af5138480562212c8d182352bfebdc88db36 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -43,6 +43,7 @@ pub enum ListAlignment { pub struct ListScrollEvent { pub visible_range: Range, pub count: usize, + pub is_scrolled: bool, } #[derive(Clone)] @@ -253,6 +254,7 @@ impl StateInner { &ListScrollEvent { visible_range, count: self.items.summary().count, + is_scrolled: self.logical_scroll_top.is_some(), }, cx, ); From fee369bca1571fd00fd9c7b59f8fbc36bb96e6e0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:26:25 -0700 Subject: [PATCH 179/334] Fix re-docking chat panel --- crates/collab_ui/src/chat_panel.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index ee0241eb1209dc6a36150c7d68b68250476c59e5..13b217eec48a9f6380facc5539b1d58831ebd84a 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -70,13 +70,6 @@ struct SerializedChatPanel { width: Option, } -#[derive(Debug)] -pub enum Event { - DockPositionChanged, - Focus, - Dismissed, -} - actions!(chat_panel, [ToggleFocus]); impl ChatPanel { @@ -140,7 +133,7 @@ impl ChatPanel { let new_dock_position = this.position(cx); if new_dock_position != old_dock_position { old_dock_position = new_dock_position; - cx.emit(Event::DockPositionChanged); + cx.emit(PanelEvent::ChangePosition); } cx.notify(); }, @@ -541,8 +534,6 @@ impl ChatPanel { } } -impl EventEmitter for ChatPanel {} - impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_stack() @@ -662,7 +653,7 @@ impl Panel for ChatPanel { if active { self.acknowledge_last_message(cx); if !is_channels_feature_enabled(cx) { - cx.emit(Event::Dismissed); + cx.emit(PanelEvent::Close); } } } From e90ddba2c3ba63e6025fac42a0abdc7e1afdbc9c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:38:22 -0700 Subject: [PATCH 180/334] Default to Zed Sans for UI --- assets/settings/default.json | 2 +- crates/gpui/src/text_system.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index bd157c3e6137ccf5e990b94a3085896563931e60..87cf0517a2b6f1a32ca8b0d33dadda2d3418f3db 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -36,7 +36,7 @@ // }, "buffer_line_height": "comfortable", // The name of a font to use for rendering text in the UI - "ui_font_family": "Zed Mono", + "ui_font_family": "Zed Sans", // The OpenType features to enable for text in the UI "ui_font_features": { // Disable ligatures: diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index d80c9163a996be96580f459c13e0e8eba8e71d10..6c84a7716f48e62fbd64dc8872dc68a7c6a7dcb2 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -59,7 +59,7 @@ impl TextSystem { fallback_font_stack: smallvec![ // TODO: This is currently Zed-specific. // We should allow GPUI users to provide their own fallback font stack. - font("Zed Mono"), + font("Zed Sans"), font("Helvetica") ], } From 898645681fe6e14e746988b376bdeffbebcb7090 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:35:33 -0700 Subject: [PATCH 181/334] Move settings subscription to dock Reduces likelihood of panels being unable to move themselves --- crates/assistant/src/assistant_panel.rs | 11 ----------- crates/collab_ui/src/chat_panel.rs | 11 ----------- crates/collab_ui/src/collab_panel.rs | 13 ------------- crates/project_panel/src/project_panel.rs | 12 ------------ crates/terminal_view/src/terminal_panel.rs | 9 --------- crates/workspace/src/dock.rs | 19 +++++++++++++------ 6 files changed, 13 insertions(+), 62 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index d4743afb714ab47be3e38b196a45174fb33c2aa5..9519cfacdb1715afd9cdd618d723547d40c231a5 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -192,17 +192,6 @@ impl AssistantPanel { retrieve_context_in_next_inline_assist: false, }; - let mut old_dock_position = this.position(cx); - this.subscriptions = - vec![cx.observe_global::(move |this, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - cx.notify(); - })]; - this }) }) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 13b217eec48a9f6380facc5539b1d58831ebd84a..e5c3a13442b8e27c60e6352130d46724fa57a069 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -127,17 +127,6 @@ impl ChatPanel { open_context_menu: None, }; - let mut old_dock_position = this.position(cx); - this.subscriptions.push(cx.observe_global::( - move |this: &mut Self, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - cx.notify(); - }, - )); this.subscriptions.push(cx.subscribe( &ActiveCall::global(cx), move |this: &mut Self, call, event: &room::Event, cx| match event { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5ad3d6cfa3213119551ed341be33816336f8ca5c..bbd24136ad3ba34f9a994f2c05e455a681931588 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -254,19 +254,6 @@ impl CollabPanel { this.update_entries(false, cx); - // Update the dock position when the setting changes. - let mut old_dock_position = this.position(cx); - this.subscriptions.push(cx.observe_global::( - move |this: &mut Self, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - cx.notify(); - }, - )); - let active_call = ActiveCall::global(cx); this.subscriptions .push(cx.observe(&this.user_store, |this, _, cx| { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 251e26ebfba004b81a49c1ce28956e01f42bbce5..5fa8fb102d2370d0d0e056008173b287103208d3 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -246,18 +246,6 @@ impl ProjectPanel { }; this.update_visible_entries(None, cx); - // Update the dock position when the setting changes. - let mut old_dock_position = this.position(cx); - ProjectPanelSettings::register(cx); - cx.observe_global::(move |this, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - }) - .detach(); - this }); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index d0b52f5eb217ed60cc9b62e20657e5033506f133..2156aad18af283c0c9176856db38cfbf9a7f7adc 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -159,15 +159,6 @@ impl TerminalPanel { height: None, _subscriptions: subscriptions, }; - let mut old_dock_position = this.position(cx); - cx.observe_global::(move |this, cx| { - let new_dock_position = this.position(cx); - if new_dock_position != old_dock_position { - old_dock_position = new_dock_position; - cx.emit(PanelEvent::ChangePosition); - } - }) - .detach(); this } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 0c752597262ca1f64a996d09795db289941a30b2..742ff3b6f859988a74d34c9f1625167a78d76927 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -7,6 +7,7 @@ use gpui::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use settings::SettingsStore; use std::sync::Arc; use ui::{h_stack, ContextMenu, IconButton, Tooltip}; use ui::{prelude::*, right_click_menu}; @@ -14,7 +15,6 @@ use ui::{prelude::*, right_click_menu}; const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.); pub enum PanelEvent { - ChangePosition, ZoomIn, ZoomOut, Activate, @@ -177,7 +177,7 @@ impl DockPosition { struct PanelEntry { panel: Arc, - _subscriptions: [Subscription; 2], + _subscriptions: [Subscription; 3], } pub struct PanelButtons { @@ -321,9 +321,15 @@ impl Dock { ) { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), - cx.subscribe(&panel, move |this, panel, event, cx| match event { - PanelEvent::ChangePosition => { + cx.observe_global::({ + let workspace = workspace.clone(); + let panel = panel.clone(); + + move |this, cx| { let new_position = panel.read(cx).position(cx); + if new_position == this.position { + return; + } let Ok(new_dock) = workspace.update(cx, |workspace, cx| { if panel.is_zoomed(cx) { @@ -354,6 +360,8 @@ impl Dock { } }); } + }), + cx.subscribe(&panel, move |this, panel, event, cx| match event { PanelEvent::ZoomIn => { this.set_panel_zoomed(&panel.to_any(), true, cx); if !panel.focus_handle(cx).contains_focused(cx) { @@ -735,9 +743,8 @@ pub mod test { true } - fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { + fn set_position(&mut self, position: DockPosition, _: &mut ViewContext) { self.position = position; - cx.emit(PanelEvent::ChangePosition); } fn size(&self, _: &WindowContext) -> Pixels { From 4d87a67af8626a43f37c3458c4e9f04ed2414d4e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 13 Jan 2024 22:44:47 -0700 Subject: [PATCH 182/334] Remove unused imports --- crates/assistant/src/assistant_panel.rs | 9 +++---- crates/collab_ui/src/chat_panel.rs | 29 +++++++--------------- crates/collab_ui/src/collab_panel.rs | 2 +- crates/project_panel/src/project_panel.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 2 +- 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 9519cfacdb1715afd9cdd618d723547d40c231a5..2b0a0941c0f014e306d522100477b1e9a9477399 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -40,7 +40,7 @@ use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset a use project::Project; use search::{buffer_search::DivRegistrar, BufferSearchBar}; use semantic_index::{SemanticIndex, SemanticIndexStatus}; -use settings::{Settings, SettingsStore}; +use settings::Settings; use std::{ cell::Cell, cmp, @@ -165,7 +165,7 @@ impl AssistantPanel { cx.on_focus_in(&focus_handle, Self::focus_in).detach(); cx.on_focus_out(&focus_handle, Self::focus_out).detach(); - let mut this = Self { + Self { workspace: workspace_handle, active_editor_index: Default::default(), prev_active_editor_index: Default::default(), @@ -190,9 +190,7 @@ impl AssistantPanel { _watch_saved_conversations, semantic_index, retrieve_context_in_next_inline_assist: false, - }; - - this + } }) }) }) @@ -3122,6 +3120,7 @@ mod tests { use crate::MessageId; use ai::test::FakeCompletionProvider; use gpui::AppContext; + use settings::SettingsStore; #[gpui::test] fn test_inserting_and_removing_messages(cx: &mut AppContext) { diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index e5c3a13442b8e27c60e6352130d46724fa57a069..c919ceac9fd679f734e3a72b2596af72d0191b56 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{collab_panel, is_channels_feature_enabled, ChatPanelSettings, CollabPanel}; +use crate::{collab_panel, is_channels_feature_enabled, ChatPanelSettings}; use anyhow::Result; use call::{room, ActiveCall}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -7,24 +7,22 @@ use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ - actions, div, list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext, - ClickEvent, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, - ListOffset, ListScrollEvent, ListState, Model, Render, Subscription, Task, View, ViewContext, - VisualContext, WeakView, + actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, DismissEvent, + ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight, ListOffset, ListScrollEvent, + ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use language::LanguageRegistry; use menu::Confirm; use message_editor::MessageEditor; use project::Fs; use rich_text::RichText; -use rpc::proto; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::Settings; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; use ui::{ - popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, Key, KeyBinding, - Label, TabBar, Tooltip, + popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, + TabBar, }; use util::{ResultExt, TryFutureExt}; use workspace::{ @@ -279,7 +277,7 @@ impl ChatPanel { fn render_message(&mut self, ix: usize, cx: &mut ViewContext) -> impl IntoElement { let active_chat = &self.active_chat.as_ref().unwrap().0; - let (message, is_continuation_from_previous, is_continuation_to_next, is_admin) = + let (message, is_continuation_from_previous, is_admin) = active_chat.update(cx, |active_chat, cx| { let is_admin = self .channel_store @@ -288,13 +286,9 @@ impl ChatPanel { let last_message = active_chat.message(ix.saturating_sub(1)); let this_message = active_chat.message(ix).clone(); - let next_message = - active_chat.message(ix.saturating_add(1).min(active_chat.message_count() - 1)); let is_continuation_from_previous = last_message.id != this_message.id && last_message.sender.id == this_message.sender.id; - let is_continuation_to_next = this_message.id != next_message.id - && this_message.sender.id == next_message.sender.id; if let ChannelMessageId::Saved(id) = this_message.id { if this_message @@ -306,12 +300,7 @@ impl ChatPanel { } } - ( - this_message, - is_continuation_from_previous, - is_continuation_to_next, - is_admin, - ) + (this_message, is_continuation_from_previous, is_admin) }); let _is_pending = message.is_pending(); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index bbd24136ad3ba34f9a994f2c05e455a681931588..38569adb20586308c5236cd67216e13cbc466e3c 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -26,7 +26,7 @@ use menu::{Cancel, Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; use rpc::proto::{self, PeerId}; use serde_derive::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::Settings; use smallvec::SmallVec; use std::{mem, sync::Arc}; use theme::{ActiveTheme, ThemeSettings}; diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 5fa8fb102d2370d0d0e056008173b287103208d3..113900dcd20360ef3f418adedf624a6e951eafcd 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1,6 +1,6 @@ pub mod file_associations; mod project_panel_settings; -use settings::{Settings, SettingsStore}; +use settings::Settings; use db::kvp::KEY_VALUE_STORE; use editor::{scroll::autoscroll::Autoscroll, Cancel, Editor}; diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 2156aad18af283c0c9176856db38cfbf9a7f7adc..dee18ea73b55f7d17c9cc742de179ed580f3d596 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -11,7 +11,7 @@ use itertools::Itertools; use project::{Fs, ProjectEntryId}; use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::Settings; use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings}; use ui::{h_stack, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip}; use util::{ResultExt, TryFutureExt}; From b34c78016fe051cef04547c89b5a74b1181c5ec3 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sun, 14 Jan 2024 12:26:54 -0700 Subject: [PATCH 183/334] Fix tests for TestPanel --- crates/workspace/src/dock.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 742ff3b6f859988a74d34c9f1625167a78d76927..52f768e03f9c53460b539d53be1729a7d68c81ae 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -743,8 +743,9 @@ pub mod test { true } - fn set_position(&mut self, position: DockPosition, _: &mut ViewContext) { + fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext) { self.position = position; + cx.update_global::(|_, _| {}); } fn size(&self, _: &WindowContext) -> Pixels { From 05d05b051b17f64fc99fa1b4888761caba0058d5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 11:40:42 +0100 Subject: [PATCH 184/334] Pop node from dispatch tree during `cx.paint_view` Co-Authored-By: Thorsten --- crates/gpui/src/window.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index b707066559dd5526b9430d298c35dbe843c1a6f9..8e38992251f5da196038f2718060d4f12a279496 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2034,6 +2034,7 @@ impl<'a> WindowContext<'a> { .dispatch_tree .push_node(None, None, Some(view_id)); let result = f(self); + self.window.next_frame.dispatch_tree.pop_node(); self.window.next_frame.view_stack.pop(); result } From 74f3366f425d070a450abadc68d774f3367efb9f Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 15 Jan 2024 13:47:09 +0100 Subject: [PATCH 185/334] Fix editor stealing click events from copy-error button Co-authored-by: Antonio --- crates/editor/src/element.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 29d3e0aaea38011a120401eb54aabb1a311bd3b6..e602b23c8e9f840d3c1e9730407d80a6193f55bf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -388,7 +388,9 @@ impl EditorElement { let mut click_count = event.click_count; let modifiers = event.modifiers; - if gutter_bounds.contains(&event.position) { + if cx.default_prevented() { + return; + } else if gutter_bounds.contains(&event.position) { click_count = 3; // Simulate triple-click when clicking the gutter to select lines } else if !text_bounds.contains(&event.position) { return; From 253c8dbe8ea6683d9915bade2f29331037392ec5 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 15 Jan 2024 15:49:23 +0200 Subject: [PATCH 186/334] Disable copilot for feedback and lsp log editors LSP log editor caused recursive flood of messages, and feedback editor is better with people writing their own feedback. --- crates/editor/src/editor.rs | 13 ++++++++++--- crates/feedback/src/feedback_modal.rs | 1 + crates/language_tools/src/lsp_log.rs | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0bfd6afa3b265dfb3845e944b6d7a916ca066261..56c5be0fbd69c64824ec9635199732d224a1bd6e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -604,6 +604,7 @@ pub struct Editor { gutter_width: Pixels, style: Option, editor_actions: Vec)>>, + show_copilot_suggestions: bool, } pub struct EditorSnapshot { @@ -1804,6 +1805,7 @@ impl Editor { gutter_width: Default::default(), style: None, editor_actions: Default::default(), + show_copilot_suggestions: mode == EditorMode::Full, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), cx.subscribe(&buffer, Self::on_buffer_event), @@ -2066,6 +2068,10 @@ impl Editor { self.read_only = read_only; } + pub fn set_show_copilot_suggestions(&mut self, show_copilot_suggestions: bool) { + self.show_copilot_suggestions = show_copilot_suggestions; + } + fn selections_did_change( &mut self, local: bool, @@ -3976,7 +3982,7 @@ impl Editor { cx: &mut ViewContext, ) -> Option<()> { let copilot = Copilot::global(cx)?; - if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { + if !self.show_copilot_suggestions || !copilot.read(cx).status().is_authorized() { self.clear_copilot_suggestions(cx); return None; } @@ -4036,7 +4042,7 @@ impl Editor { cx: &mut ViewContext, ) -> Option<()> { let copilot = Copilot::global(cx)?; - if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { + if !self.show_copilot_suggestions || !copilot.read(cx).status().is_authorized() { return None; } @@ -4161,7 +4167,8 @@ impl Editor { let file = snapshot.file_at(location); let language = snapshot.language_at(location); let settings = all_language_settings(file, cx); - settings.copilot_enabled(language, file.map(|f| f.path().as_ref())) + self.show_copilot_suggestions + && settings.copilot_enabled(language, file.map(|f| f.path().as_ref())) } fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool { diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 2444a8e94850f8128aafb0948cb356eee81f1086..38e06376cb6a46795181a3498a0d048b7a968e39 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -186,6 +186,7 @@ impl FeedbackModal { cx, ); editor.set_show_gutter(false, cx); + editor.set_show_copilot_suggestions(false); editor.set_vertical_scroll_margin(5, cx); editor }); diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index e3718267582b5331faa0dc71be3338a93022730f..62aead0857c87e6f88482991b4d7f18dae34ca59 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -449,6 +449,7 @@ impl LspLogView { editor.set_text(log_contents, cx); editor.move_to_end(&MoveToEnd, cx); editor.set_read_only(true); + editor.set_show_copilot_suggestions(false); editor }); let editor_subscription = cx.subscribe( From 346103dfb40555a3315352559a7f8afc839432b2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 15 Jan 2024 16:20:53 +0200 Subject: [PATCH 187/334] Do not run squawk tests outside of PR builds --- script/squawk | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/script/squawk b/script/squawk index e4ade6fbed6e77dd7fc791ed33988c02020f0078..0fb3e5a3325e8005fe4f0667debc7626cab5c9bd 100755 --- a/script/squawk +++ b/script/squawk @@ -6,6 +6,11 @@ set -e +if [ -z "$GITHUB_BASE_REF" ]; then + echo 'Not a pull request, skipping squawk modified migrations linting' + return 0 +fi + SQUAWK_VERSION=0.26.0 SQUAWK_BIN="./target/squawk-$SQUAWK_VERSION" SQUAWK_ARGS="--assume-in-transaction" From e52a2298cc517403c120a85f7ae313e460e9fa42 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:46:18 +0100 Subject: [PATCH 188/334] gpui: Pin to font-kit with improved OTC parsing performance. (#4047) Details are in https://github.com/zed-industries/font-kit/pull/1; We're not doing anything too fancy, really. Still, you should mostly see font loading times drop significantly for font collections Release Notes: - Improved loading performance of large font collections (e.g. Iosevka). Fixes https://github.com/zed-industries/community/issues/1745, https://github.com/zed-industries/community/issues/246 https://github.com/zed-industries/zed/assets/24362066/f70edbad-ded6-4c12-9c6d-7a487f330a1b --- Cargo.lock | 2 +- crates/gpui/Cargo.toml | 2 +- crates/gpui/src/platform/mac/open_type.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4bac6d7c500f4813e73402ec06f47051876788b..7c240ac8a1e2cfdb714ea9f26cb6ee603539aedc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2610,7 +2610,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-kit" version = "0.11.0" -source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" +source = "git+https://github.com/zed-industries/font-kit?rev=d97147f#d97147ff11a9024b9707d9c9c7e3a0bdaba048ac" dependencies = [ "bitflags 1.3.2", "byteorder", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 118753aafea5981e0dd2119f9b4203c70b85695d..333f5a1649f3c73ec8a31664dfc7a74d88158cb3 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -78,7 +78,7 @@ cocoa = "0.24" core-foundation = { version = "0.9.3", features = ["with-uuid"] } core-graphics = "0.22.3" core-text = "19.2" -font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" } +font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "d97147f" } foreign-types = "0.3" log.workspace = true metal = "0.21.0" diff --git a/crates/gpui/src/platform/mac/open_type.rs b/crates/gpui/src/platform/mac/open_type.rs index 50e93a866db6c569926b791e5c89d82be7f8199b..c9d7197c0d3aa87d33d2e10048b14aa6c77483ec 100644 --- a/crates/gpui/src/platform/mac/open_type.rs +++ b/crates/gpui/src/platform/mac/open_type.rs @@ -378,7 +378,7 @@ fn toggle_open_type_feature( new_descriptor.as_concrete_TypeRef(), ); let new_font = CTFont::wrap_under_create_rule(new_font); - *font = Font::from_native_font(new_font); + *font = Font::from_native_font(&new_font); } } } From 5000a53a6100e8fb7bfc5cfcbd09f082fcb93c17 Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 15 Jan 2024 10:19:08 -0500 Subject: [PATCH 189/334] Prevent storybook dialog from swallowing terminal cursor when ctrl-c-ed --- Cargo.lock | 88 +++++++++++++++++++++++++++++++ crates/storybook/Cargo.toml | 1 + crates/storybook/src/storybook.rs | 12 +++-- 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4bac6d7c500f4813e73402ec06f47051876788b..3abb269c857e865069fb6392cea9b7b7f2b79e42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1949,6 +1949,16 @@ dependencies = [ "syn 2.0.37", ] +[[package]] +name = "ctrlc" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +dependencies = [ + "nix 0.27.1", + "windows-sys 0.52.0", +] + [[package]] name = "curl" version = "0.4.44" @@ -4489,6 +4499,17 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "node_runtime" version = "0.1.0" @@ -7452,6 +7473,7 @@ dependencies = [ "chrono", "clap 4.4.4", "collab_ui", + "ctrlc", "dialoguer", "editor", "fuzzy", @@ -9289,6 +9311,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -9319,6 +9350,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -9331,6 +9377,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -9343,6 +9395,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -9355,6 +9413,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -9367,6 +9431,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -9379,6 +9449,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -9391,6 +9467,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -9403,6 +9485,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.15" diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index 9f08556757b6b59d1aed6fa7361029a36664a40a..fec2d42e5980b9e8b2ebb93f865dd74070aa3bc1 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -35,6 +35,7 @@ menu = { path = "../menu" } ui = { path = "../ui", features = ["stories"] } util = { path = "../util" } picker = { path = "../picker" } +ctrlc = "3.4" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index ceab82e12b02d0080c2374517317bb2401e1556b..7da1d67b307e660963c34297fc1fcfcca0c4222b 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -44,11 +44,17 @@ fn main() { let story_selector = args.story.clone().unwrap_or_else(|| { let stories = ComponentStory::iter().collect::>(); - let selection = FuzzySelect::new() + ctrlc::set_handler(move || {}).unwrap(); + + let result = FuzzySelect::new() .with_prompt("Choose a story to run:") .items(&stories) - .interact() - .unwrap(); + .interact(); + + let Ok(selection) = result else { + dialoguer::console::Term::stderr().show_cursor().unwrap(); + std::process::exit(0); + }; StorySelector::Component(stories[selection]) }); From 0ff5603dc9623288154932143c1ea880564065ed Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 16:23:22 +0100 Subject: [PATCH 190/334] Rebuild shader header when cbindgen sources have changed Co-Authored-By: Thorsten --- crates/gpui/build.rs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 24e493cb812d6e7478b266cc621eea7cbc77b051..44228b2e75229d36907eabcc82409e1f3eb79cb7 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -70,13 +70,23 @@ fn generate_shader_bindings() -> PathBuf { ]); config.no_includes = true; config.enumeration.prefix_with_name = true; - cbindgen::Builder::new() - .with_src(crate_dir.join("src/scene.rs")) - .with_src(crate_dir.join("src/geometry.rs")) - .with_src(crate_dir.join("src/color.rs")) - .with_src(crate_dir.join("src/window.rs")) - .with_src(crate_dir.join("src/platform.rs")) - .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs")) + + let mut builder = cbindgen::Builder::new(); + + let src_paths = [ + crate_dir.join("src/scene.rs"), + crate_dir.join("src/geometry.rs"), + crate_dir.join("src/color.rs"), + crate_dir.join("src/window.rs"), + crate_dir.join("src/platform.rs"), + crate_dir.join("src/platform/mac/metal_renderer.rs"), + ]; + for src_path in src_paths { + println!("cargo:rerun-if-changed={}", src_path.display()); + builder = builder.with_src(src_path); + } + + builder .with_config(config) .generate() .expect("Unable to generate bindings") From b136d21ebf1332f918eac2e636030ee11c833d65 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 15 Jan 2024 10:43:03 -0500 Subject: [PATCH 191/334] Make tab close button square (#4052) This PR makes the close button for tabs square. `IconButton` now accepts a `shape`, and using `IconButtonShape::Square` will ensure the `IconButton` is square with respect to its contained icon. #### Before Screenshot 2024-01-15 at 10 32 40 AM #### After Screenshot 2024-01-15 at 10 32 24 AM Release Notes: - Changed the tab close button to be square. --- .../ui/src/components/button/button_like.rs | 9 ++++- .../ui/src/components/button/icon_button.rs | 39 ++++++++++++++----- crates/ui/src/components/stories/tab.rs | 3 +- crates/workspace/src/pane.rs | 5 ++- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 3e4b478a9a237c44b95e5de09158e35e40e55449..018d31dafdb4491bc88733117c8e42f66e789aa3 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -300,6 +300,7 @@ pub struct ButtonLike { pub(super) selected: bool, pub(super) selected_style: Option, pub(super) width: Option, + pub(super) height: Option, size: ButtonSize, rounding: Option, tooltip: Option AnyView>>, @@ -317,6 +318,7 @@ impl ButtonLike { selected: false, selected_style: None, width: None, + height: None, size: ButtonSize::Default, rounding: Some(ButtonLikeRounding::All), tooltip: None, @@ -325,6 +327,11 @@ impl ButtonLike { } } + pub(crate) fn height(mut self, height: DefiniteLength) -> Self { + self.height = Some(height); + self + } + pub(crate) fn rounding(mut self, rounding: impl Into>) -> Self { self.rounding = rounding.into(); self @@ -417,7 +424,7 @@ impl RenderOnce for ButtonLike { .id(self.id.clone()) .group("") .flex_none() - .h(self.size.height()) + .h(self.height.unwrap_or(self.size.height().into())) .when_some(self.width, |this, width| this.w(width).justify_center()) .when_some(self.rounding, |this, rounding| match rounding { ButtonLikeRounding::All => this.rounded_md(), diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index 7c5313497c77ff6532838966001d2cbd4a3688e0..1e37a872922b4473616b551352f8100bbd9b1327 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -5,9 +5,17 @@ use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSiz use super::button_icon::ButtonIcon; +/// The shape of an [`IconButton`]. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub enum IconButtonShape { + Square, + Wide, +} + #[derive(IntoElement)] pub struct IconButton { base: ButtonLike, + shape: IconButtonShape, icon: IconName, icon_size: IconSize, icon_color: Color, @@ -18,6 +26,7 @@ impl IconButton { pub fn new(id: impl Into, icon: IconName) -> Self { Self { base: ButtonLike::new(id), + shape: IconButtonShape::Wide, icon, icon_size: IconSize::default(), icon_color: Color::Default, @@ -25,6 +34,11 @@ impl IconButton { } } + pub fn shape(mut self, shape: IconButtonShape) -> Self { + self.shape = shape; + self + } + pub fn icon_size(mut self, icon_size: IconSize) -> Self { self.icon_size = icon_size; self @@ -118,14 +132,21 @@ impl RenderOnce for IconButton { let is_selected = self.base.selected; let selected_style = self.base.selected_style; - self.base.child( - ButtonIcon::new(self.icon) - .disabled(is_disabled) - .selected(is_selected) - .selected_icon(self.selected_icon) - .when_some(selected_style, |this, style| this.selected_style(style)) - .size(self.icon_size) - .color(self.icon_color), - ) + self.base + .map(|this| match self.shape { + IconButtonShape::Square => this + .width(self.icon_size.rems().into()) + .height(self.icon_size.rems().into()), + IconButtonShape::Wide => this, + }) + .child( + ButtonIcon::new(self.icon) + .disabled(is_disabled) + .selected(is_selected) + .selected_icon(self.selected_icon) + .when_some(selected_style, |this, style| this.selected_style(style)) + .size(self.icon_size) + .color(self.icon_color), + ) } } diff --git a/crates/ui/src/components/stories/tab.rs b/crates/ui/src/components/stories/tab.rs index bd7b602620938775b32be3d46a41b519f2639f63..9c5c694439f74862d93e7c37155a2c340354e95b 100644 --- a/crates/ui/src/components/stories/tab.rs +++ b/crates/ui/src/components/stories/tab.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use gpui::Render; use story::Story; -use crate::{prelude::*, TabPosition}; +use crate::{prelude::*, IconButtonShape, TabPosition}; use crate::{Indicator, Tab}; pub struct TabStory; @@ -28,6 +28,7 @@ impl Render for TabStory { Tab::new("tab_1") .end_slot( IconButton::new("close_button", IconName::Close) + .shape(IconButtonShape::Square) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall), diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1b95671398a613a01f41590bdce20e0a846592d0..90b27e094b612a5f461fe061cb0c2a77693464e4 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -32,8 +32,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconName, IconSize, Indicator, - Label, Tab, TabBar, TabPosition, Tooltip, + prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconButtonShape, IconName, + IconSize, Indicator, Label, Tab, TabBar, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; @@ -1341,6 +1341,7 @@ impl Pane { .start_slot::(indicator) .end_slot( IconButton::new("close tab", IconName::Close) + .shape(IconButtonShape::Square) .icon_color(Color::Muted) .size(ButtonSize::None) .icon_size(IconSize::XSmall) From 90f4c70a82196af4f545b619cf20096c54bc0897 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 15 Jan 2024 11:34:06 -0500 Subject: [PATCH 192/334] Rename `h_stack` and `v_stack` to `h_flex` and `v_flex`, respectively (#4053) This PR renames the `h_stack` and `v_stack` to `h_flex` and `v_flex`, respectively. We were previously using `h_stack` and `v_stack` to match SwiftUI, but `h_flex` and `v_flex` fit better with the web/flexbox terminology that the rest of GPUI uses. Additionally, we were already calling the utility functions used to implement `h_stack` and `v_stack` by the new names. Release Notes: - N/A --- .../src/activity_indicator.rs | 2 +- crates/assistant/src/assistant_panel.rs | 22 +++++----- crates/auto_update/src/update_notification.rs | 6 +-- crates/breadcrumbs/src/breadcrumbs.rs | 4 +- crates/collab_ui/src/chat_panel.rs | 14 +++---- crates/collab_ui/src/collab_panel.rs | 40 +++++++++--------- .../src/collab_panel/channel_modal.rs | 14 +++---- .../src/collab_panel/contact_finder.rs | 6 +-- crates/collab_ui/src/collab_titlebar_item.rs | 14 +++---- crates/collab_ui/src/notification_panel.rs | 18 ++++---- .../src/notifications/collab_notification.rs | 6 +-- .../incoming_call_notification.rs | 2 +- .../stories/collab_notification.rs | 2 +- crates/command_palette/src/command_palette.rs | 6 +-- crates/copilot_ui/src/sign_in.rs | 10 ++--- crates/diagnostics/src/diagnostics.rs | 16 +++---- crates/diagnostics/src/items.rs | 14 +++---- crates/editor/src/editor.rs | 10 ++--- crates/editor/src/element.rs | 12 +++--- crates/editor/src/items.rs | 4 +- crates/feedback/src/feedback_modal.rs | 10 ++--- crates/file_finder/src/file_finder.rs | 4 +- crates/go_to_line/src/go_to_line.rs | 8 ++-- .../src/language_selector.rs | 2 +- crates/language_tools/src/lsp_log.rs | 4 +- crates/language_tools/src/syntax_tree_view.rs | 4 +- crates/outline/src/outline.rs | 2 +- crates/picker/src/picker.rs | 8 ++-- crates/project_panel/src/project_panel.rs | 6 +-- crates/project_symbols/src/project_symbols.rs | 4 +- .../quick_action_bar/src/quick_action_bar.rs | 2 +- crates/recent_projects/src/recent_projects.rs | 4 +- crates/search/src/buffer_search.rs | 16 +++---- crates/search/src/project_search.rs | 42 +++++++++---------- crates/story/src/story.rs | 8 ++-- .../storybook/src/stories/overflow_scroll.rs | 4 +- crates/storybook/src/stories/text.rs | 2 +- crates/terminal_view/src/terminal_panel.rs | 4 +- crates/terminal_view/src/terminal_view.rs | 4 +- crates/theme_selector/src/theme_selector.rs | 4 +- crates/ui/src/components/button/button.rs | 4 +- crates/ui/src/components/checkbox.rs | 2 +- crates/ui/src/components/context_menu.rs | 8 ++-- crates/ui/src/components/keybinding.rs | 6 +-- crates/ui/src/components/list/list.rs | 4 +- crates/ui/src/components/list/list_header.rs | 8 ++-- crates/ui/src/components/list/list_item.rs | 12 +++--- .../ui/src/components/list/list_sub_header.rs | 4 +- crates/ui/src/components/popover.rs | 6 +-- crates/ui/src/components/stack.rs | 4 +- crates/ui/src/components/stories/checkbox.rs | 6 +-- crates/ui/src/components/stories/list_item.rs | 2 +- crates/ui/src/components/stories/tab.rs | 14 +++---- crates/ui/src/components/stories/tab_bar.rs | 2 +- .../src/components/stories/toggle_button.rs | 4 +- crates/ui/src/components/tab.rs | 6 +-- crates/ui/src/components/tab_bar.rs | 6 +-- crates/ui/src/components/tooltip.rs | 6 +-- crates/ui/src/prelude.rs | 2 +- crates/vcs_menu/src/lib.rs | 10 ++--- crates/welcome/src/base_keymap_picker.rs | 2 +- crates/welcome/src/welcome.rs | 16 +++---- crates/workspace/src/dock.rs | 4 +- crates/workspace/src/modal_layer.rs | 6 +-- crates/workspace/src/notifications.rs | 6 +-- crates/workspace/src/pane.rs | 8 ++-- crates/workspace/src/shared_screen.rs | 4 +- crates/workspace/src/status_bar.rs | 6 +-- crates/workspace/src/toolbar.rs | 10 ++--- 69 files changed, 271 insertions(+), 271 deletions(-) diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index d04a5ab319f4c116a319a0a6f4d7b51a1de51184..ac63592d6bc21bc6ba46649eb1924d1cc5566d89 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -295,7 +295,7 @@ impl Render for ActivityIndicator { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let content = self.content_to_render(cx); - let mut result = h_stack() + let mut result = h_flex() .id("activity-indicator") .on_action(cx.listener(Self::show_error_message)) .on_action(cx.listener(Self::dismiss_error_message)); diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 2b0a0941c0f014e306d522100477b1e9a9477399..7ae4955483f42358f1eeb67c5fad5b330d228204 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1090,7 +1090,7 @@ fn build_api_key_editor(cx: &mut ViewContext) -> View { impl Render for AssistantPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { if let Some(api_key_editor) = self.api_key_editor.clone() { - v_stack() + v_flex() .on_action(cx.listener(AssistantPanel::save_credentials)) .track_focus(&self.focus_handle) .child(Label::new( @@ -1115,26 +1115,26 @@ impl Render for AssistantPanel { } else { let header = TabBar::new("assistant_header") .start_child( - h_stack().gap_1().child(Self::render_hamburger_button(cx)), // .children(title), + h_flex().gap_1().child(Self::render_hamburger_button(cx)), // .children(title), ) .children(self.active_editor().map(|editor| { - h_stack() + h_flex() .h(rems(Tab::HEIGHT_IN_REMS)) .flex_1() .px_2() .child(Label::new(editor.read(cx).title(cx)).into_element()) })) .end_child(if self.focus_handle.contains_focused(cx) { - h_stack() + h_flex() .gap_2() - .child(h_stack().gap_1().children(self.render_editor_tools(cx))) + .child(h_flex().gap_1().children(self.render_editor_tools(cx))) .child( ui::Divider::vertical() .inset() .color(ui::DividerColor::Border), ) .child( - h_stack() + h_flex() .gap_1() .child(Self::render_plus_button(cx)) .child(self.render_zoom_button(cx)), @@ -1153,7 +1153,7 @@ impl Render for AssistantPanel { } else { div() }; - v_stack() + v_flex() .key_context("AssistantPanel") .size_full() .on_action(cx.listener(|this, _: &workspace::NewFile, cx| { @@ -2530,7 +2530,7 @@ impl Render for ConversationEditor { .child(self.editor.clone()), ) .child( - h_stack() + h_flex() .absolute() .gap_1() .top_3() @@ -2616,7 +2616,7 @@ impl EventEmitter for InlineAssistant {} impl Render for InlineAssistant { fn render(&mut self, cx: &mut ViewContext) -> impl Element { let measurements = self.measurements.get(); - h_stack() + h_flex() .w_full() .py_2() .border_y_1() @@ -2628,7 +2628,7 @@ impl Render for InlineAssistant { .on_action(cx.listener(Self::move_up)) .on_action(cx.listener(Self::move_down)) .child( - h_stack() + h_flex() .justify_center() .w(measurements.gutter_width) .child( @@ -2676,7 +2676,7 @@ impl Render for InlineAssistant { }), ) .child( - h_stack() + h_flex() .w_full() .ml(measurements.anchor_x - measurements.gutter_width) .child(self.render_prompt_editor(cx)), diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index 65f786bca4c4e1a729974459fbdf8e549a9893cc..985972da56364c646850fe7146f3323d06f56015 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -4,7 +4,7 @@ use gpui::{ }; use menu::Cancel; use util::channel::ReleaseChannel; -use workspace::ui::{h_stack, v_stack, Icon, IconName, Label, StyledExt}; +use workspace::ui::{h_flex, v_flex, Icon, IconName, Label, StyledExt}; pub struct UpdateNotification { version: SemanticVersion, @@ -16,12 +16,12 @@ impl Render for UpdateNotification { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { let app_name = cx.global::().display_name(); - v_stack() + v_flex() .on_action(cx.listener(UpdateNotification::dismiss)) .elevation_3(cx) .p_4() .child( - h_stack() + h_flex() .justify_between() .child(Label::new(format!( "Updated to {app_name} {}", diff --git a/crates/breadcrumbs/src/breadcrumbs.rs b/crates/breadcrumbs/src/breadcrumbs.rs index e41c0c06b180cab749f7e34bb1199bd28709201b..ca07a205ac971ce5d7fd206704f7cce3b0ae7d70 100644 --- a/crates/breadcrumbs/src/breadcrumbs.rs +++ b/crates/breadcrumbs/src/breadcrumbs.rs @@ -31,7 +31,7 @@ impl EventEmitter for Breadcrumbs {} impl Render for Breadcrumbs { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let element = h_stack().text_ui(); + let element = h_flex().text_ui(); let Some(active_item) = self.active_item.as_ref() else { return element; }; @@ -51,7 +51,7 @@ impl Render for Breadcrumbs { Label::new("›").color(Color::Muted).into_any_element() }); - let breadcrumbs_stack = h_stack().gap_1().children(breadcrumbs); + let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs); match active_item .downcast::() .map(|editor| editor.downgrade()) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index c919ceac9fd679f734e3a72b2596af72d0191b56..cc271a5f5e13bec9d083d65d96974decc71967d6 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -325,13 +325,13 @@ impl ChatPanel { }; let this = cx.view().clone(); - v_stack() + v_flex() .w_full() .relative() .overflow_hidden() .when(!is_continuation_from_previous, |this| { this.pt_3().child( - h_stack() + h_flex() .child( div().absolute().child( Avatar::new(message.sender.avatar_uri.clone()) @@ -358,7 +358,7 @@ impl ChatPanel { }) .when(is_continuation_from_previous, |this| this.pt_1()) .child( - v_stack() + v_flex() .w_full() .text_ui_sm() .id(element_id) @@ -514,14 +514,14 @@ impl ChatPanel { impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .track_focus(&self.focus_handle) .full() .on_action(cx.listener(Self::send)) .child( - h_stack().z_index(1).child( + h_flex().z_index(1).child( TabBar::new("chat_header").child( - h_stack() + h_flex() .w_full() .h(rems(ui::Tab::HEIGHT_IN_REMS)) .px_2() @@ -567,7 +567,7 @@ impl Render for ChatPanel { } })) .child( - h_stack() + h_flex() .when(!self.is_scrolled_to_bottom, |el| { el.border_t_1().border_color(cx.theme().colors().border) }) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index c9e1ae1bb8a9c28afeac1434e53cab55c1fc0041..9cb447153c1bc39b9d291a2e17020cae788b43ca 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -887,7 +887,7 @@ impl CollabPanel { .ok(); })) .start_slot( - h_stack() + h_flex() .gap_1() .child(render_tree_branch(is_last, false, cx)) .child(IconButton::new(0, IconName::Folder)), @@ -908,7 +908,7 @@ impl CollabPanel { ListItem::new(("screen", id)) .selected(is_selected) .start_slot( - h_stack() + h_flex() .gap_1() .child(render_tree_branch(is_last, false, cx)) .child(IconButton::new(0, IconName::Screen)), @@ -949,7 +949,7 @@ impl CollabPanel { this.open_channel_notes(channel_id, cx); })) .start_slot( - h_stack() + h_flex() .gap_1() .child(render_tree_branch(false, true, cx)) .child(IconButton::new(0, IconName::File)), @@ -970,7 +970,7 @@ impl CollabPanel { this.join_channel_chat(channel_id, cx); })) .start_slot( - h_stack() + h_flex() .gap_1() .child(render_tree_branch(false, false, cx)) .child(IconButton::new(0, IconName::MessageBubbles)), @@ -1726,12 +1726,12 @@ impl CollabPanel { fn render_signed_out(&mut self, cx: &mut ViewContext) -> Div { let collab_blurb = "Work with your team in realtime with collaborative editing, voice, shared notes and more."; - v_stack() + v_flex() .gap_6() .p_4() .child(Label::new(collab_blurb)) .child( - v_stack() + v_flex() .gap_2() .child( Button::new("sign_in", "Sign in") @@ -1832,14 +1832,14 @@ impl CollabPanel { } fn render_signed_in(&mut self, cx: &mut ViewContext) -> Div { - v_stack() + v_flex() .size_full() .child(list(self.list_state.clone()).full()) .child( - v_stack() + v_flex() .child(div().mx_2().border_primary(cx).border_t()) .child( - v_stack() + v_flex() .p_2() .child(self.render_filter_input(&self.filter_editor, cx)), ), @@ -1961,7 +1961,7 @@ impl CollabPanel { | Section::Offline => true, }; - h_stack() + h_flex() .w_full() .group("section-header") .child( @@ -2007,7 +2007,7 @@ impl CollabPanel { .selected(is_selected) .on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(Label::new(github_login.clone())) @@ -2105,11 +2105,11 @@ impl CollabPanel { .indent_step_size(px(20.)) .selected(is_selected) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(Label::new(github_login.clone())) - .child(h_stack().children(controls)), + .child(h_flex().children(controls)), ) .start_slot(Avatar::new(user.avatar_uri.clone())) } @@ -2149,11 +2149,11 @@ impl CollabPanel { ListItem::new(("channel-invite", channel.id as usize)) .selected(is_selected) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(Label::new(channel.name.clone())) - .child(h_stack().children(controls)), + .child(h_flex().children(controls)), ) .start_slot( Icon::new(IconName::Hash) @@ -2289,21 +2289,21 @@ impl CollabPanel { .color(Color::Muted), ) .child( - h_stack() + h_flex() .id(channel_id as usize) .child(Label::new(channel.name.clone())) .children(face_pile.map(|face_pile| face_pile.render(cx))), ), ) .child( - h_stack() + h_flex() .absolute() .right(rems(0.)) .h_full() // HACK: Without this the channel name clips on top of the icons, but I'm not sure why. .z_index(10) .child( - h_stack() + h_flex() .h_full() .gap_1() .px_1() @@ -2410,7 +2410,7 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) -> impl Render for CollabPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .key_context("CollabPanel") .on_action(cx.listener(CollabPanel::cancel)) .on_action(cx.listener(CollabPanel::select_next)) @@ -2603,7 +2603,7 @@ struct DraggedChannelView { impl Render for DraggedChannelView { fn render(&mut self, cx: &mut ViewContext) -> impl Element { let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); - h_stack() + h_flex() .font(ui_font) .bg(cx.theme().colors().background) .w(self.width) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 8020613c1ae2a4cc2ac9e0ad61293541c451f0aa..c207e31bbeaa2b087578860c09ae176827074388 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -152,19 +152,19 @@ impl Render for ChannelModal { let visibility = channel.visibility; let mode = self.picker.read(cx).delegate.mode; - v_stack() + v_flex() .key_context("ChannelModal") .on_action(cx.listener(Self::toggle_mode)) .on_action(cx.listener(Self::dismiss)) .elevation_3(cx) .w(rems(34.)) .child( - v_stack() + v_flex() .px_2() .py_1() .gap_2() .child( - h_stack() + h_flex() .w_px() .flex_1() .gap_1() @@ -172,13 +172,13 @@ impl Render for ChannelModal { .child(Label::new(channel_name)), ) .child( - h_stack() + h_flex() .w_full() .h(rems(22. / 16.)) .justify_between() .line_height(rems(1.25)) .child( - h_stack() + h_flex() .gap_2() .child( Checkbox::new( @@ -212,7 +212,7 @@ impl Render for ChannelModal { ), ) .child( - h_stack() + h_flex() .child( div() .id("manage-members") @@ -391,7 +391,7 @@ impl PickerDelegate for ChannelModalDelegate { .selected(selected) .start_slot(Avatar::new(user.avatar_uri.clone())) .child(Label::new(user.github_login.clone())) - .end_slot(h_stack().gap_2().map(|slot| { + .end_slot(h_flex().gap_2().map(|slot| { match self.mode { Mode::ManageMembers => slot .children( diff --git a/crates/collab_ui/src/collab_panel/contact_finder.rs b/crates/collab_ui/src/collab_panel/contact_finder.rs index b769ec7e7f394fb7a94c38f6220b507d43e77ba1..2c59df2eb53f7fa209bca449daefc838f9662e9c 100644 --- a/crates/collab_ui/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui/src/collab_panel/contact_finder.rs @@ -36,17 +36,17 @@ impl ContactFinder { impl Render for ContactFinder { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .elevation_3(cx) .child( - v_stack() + v_flex() .px_2() .py_1() .bg(cx.theme().colors().element_background) // HACK: Prevent the background color from overflowing the parent container. .rounded_t(px(8.)) .child(Label::new("Contacts")) - .child(h_stack().child(Label::new("Invite new contacts"))), + .child(h_flex().child(Label::new("Invite new contacts"))), ) .child(self.picker.clone()) .w(rems(34.)) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 03dfd450704153b31c52ec3d0baad0d215389190..e4ab39698f92118102bad10b154f8b09b0c14bc6 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -14,7 +14,7 @@ use rpc::proto; use std::sync::Arc; use theme::{ActiveTheme, PlayerColors}; use ui::{ - h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, + h_flex, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, Tooltip, }; use util::ResultExt; @@ -58,7 +58,7 @@ impl Render for CollabTitlebarItem { let client = self.client.clone(); let project_id = self.project.read(cx).remote_id(); - h_stack() + h_flex() .id("titlebar") .justify_between() .w_full() @@ -83,7 +83,7 @@ impl Render for CollabTitlebarItem { }) // left side .child( - h_stack() + h_flex() .gap_1() .children(self.render_project_host(cx)) .child(self.render_project_name(cx)) @@ -128,7 +128,7 @@ impl Render for CollabTitlebarItem { )?; Some( - v_stack() + v_flex() .id(("collaborator", collaborator.user.id)) .child(face_pile) .child(render_color_ribbon( @@ -160,7 +160,7 @@ impl Render for CollabTitlebarItem { ) // right side .child( - h_stack() + h_flex() .gap_1() .pr_1() .when_some(room, |this, room| { @@ -634,7 +634,7 @@ impl CollabTitlebarItem { .trigger( ButtonLike::new("user-menu") .child( - h_stack() + h_flex() .gap_0p5() .child(Avatar::new(user.avatar_uri.clone())) .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), @@ -657,7 +657,7 @@ impl CollabTitlebarItem { .trigger( ButtonLike::new("user-menu") .child( - h_stack() + h_flex() .gap_0p5() .child(Icon::new(IconName::ChevronDown).color(Color::Muted)), ) diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 95473044a3f4242cd497ce0087fe1e47e8865d6f..72c7d4fe6980a856b6d5a0745d86772c87feae48 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::{sync::Arc, time::Duration}; use time::{OffsetDateTime, UtcOffset}; -use ui::{h_stack, prelude::*, v_stack, Avatar, Button, Icon, IconButton, IconName, Label}; +use ui::{h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -251,13 +251,13 @@ impl NotificationPanel { .rounded_full() })) .child( - v_stack() + v_flex() .gap_1() .size_full() .overflow_hidden() .child(Label::new(text.clone())) .child( - h_stack() + h_flex() .child( Label::new(format_timestamp( timestamp, @@ -276,7 +276,7 @@ impl NotificationPanel { ))) } else if needs_response { Some( - h_stack() + h_flex() .flex_grow() .justify_end() .child(Button::new("decline", "Decline").on_click({ @@ -541,10 +541,10 @@ impl NotificationPanel { impl Render for NotificationPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .size_full() .child( - h_stack() + h_flex() .justify_between() .px_2() .py_1() @@ -558,7 +558,7 @@ impl Render for NotificationPanel { .map(|this| { if self.client.user_id().is_none() { this.child( - v_stack() + v_flex() .gap_2() .p_4() .child( @@ -592,7 +592,7 @@ impl Render for NotificationPanel { ) } else if self.notification_list.item_count() == 0 { this.child( - v_stack().p_4().child( + v_flex().p_4().child( div().flex().w_full().items_center().child( Label::new("You have no notifications.") .color(Color::Muted) @@ -711,7 +711,7 @@ impl Render for NotificationToast { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let user = self.actor.clone(); - h_stack() + h_flex() .id("notification_panel_toast") .children(user.map(|user| Avatar::new(user.avatar_uri.clone()))) .child(Label::new(self.text.clone())) diff --git a/crates/collab_ui/src/notifications/collab_notification.rs b/crates/collab_ui/src/notifications/collab_notification.rs index fa0b0a1b14782b8bbe586348487228d75df743f7..8157bc1318dac52710732880d565e4914d01d8de 100644 --- a/crates/collab_ui/src/notifications/collab_notification.rs +++ b/crates/collab_ui/src/notifications/collab_notification.rs @@ -33,7 +33,7 @@ impl ParentElement for CollabNotification { impl RenderOnce for CollabNotification { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_stack() + h_flex() .text_ui() .justify_between() .size_full() @@ -42,9 +42,9 @@ impl RenderOnce for CollabNotification { .p_2() .gap_2() .child(img(self.avatar_uri).w_12().h_12().rounded_full()) - .child(v_stack().overflow_hidden().children(self.children)) + .child(v_flex().overflow_hidden().children(self.children)) .child( - v_stack() + v_flex() .child(self.accept_button) .child(self.dismiss_button), ) diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index 93df9a4be5445bd67072affaaf2093e7fd2a32a1..f66194c52a0006c46c37cba2514e505c4ee5ec9b 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -137,7 +137,7 @@ impl Render for IncomingCallNotification { move |_, cx| state.respond(false, cx) }), ) - .child(v_stack().overflow_hidden().child(Label::new(format!( + .child(v_flex().overflow_hidden().child(Label::new(format!( "{} is sharing a project in Zed", self.state.call.calling_user.github_login )))), diff --git a/crates/collab_ui/src/notifications/stories/collab_notification.rs b/crates/collab_ui/src/notifications/stories/collab_notification.rs index c43cac46d21352ac8375e33cf530891385c36da0..e67ce817b69db6c4c6e0c24c2093b8cbb708e153 100644 --- a/crates/collab_ui/src/notifications/stories/collab_notification.rs +++ b/crates/collab_ui/src/notifications/stories/collab_notification.rs @@ -24,7 +24,7 @@ impl Render for CollabNotificationStory { Button::new("decline", "Decline"), ) .child( - v_stack() + v_flex() .overflow_hidden() .child(Label::new("maxdeviant is sharing a project in Zed")), ), diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index bbc2cd412305aff870f712187c4b7e13d916303d..e077426bb845dbc54efb9fc06906084946d41843 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -11,7 +11,7 @@ use gpui::{ }; use picker::{Picker, PickerDelegate}; -use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing}; +use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing}; use util::{ channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, ResultExt, @@ -84,7 +84,7 @@ impl FocusableView for CommandPalette { impl Render for CommandPalette { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } @@ -311,7 +311,7 @@ impl PickerDelegate for CommandPaletteDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(HighlightedLabel::new( diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index ae2085aaf3e2576b65ea2d9b94fd2a4b62208fb4..f78a82699dc3c70925accc3e70a3242e9aad5061 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -57,7 +57,7 @@ impl CopilotCodeVerification { .read_from_clipboard() .map(|item| item.text() == &data.user_code) .unwrap_or(false); - h_stack() + h_flex() .w_full() .p_1() .border() @@ -90,7 +90,7 @@ impl CopilotCodeVerification { } else { "Connect to Github" }; - v_stack() + v_flex() .flex_1() .gap_2() .items_center() @@ -118,7 +118,7 @@ impl CopilotCodeVerification { ) } fn render_enabled_modal(cx: &mut ViewContext) -> impl Element { - v_stack() + v_flex() .gap_2() .child(Headline::new("Copilot Enabled!").size(HeadlineSize::Large)) .child(Label::new( @@ -132,7 +132,7 @@ impl CopilotCodeVerification { } fn render_unauthorized_modal() -> impl Element { - v_stack() + v_flex() .child(Headline::new("You must have an active GitHub Copilot subscription.").size(HeadlineSize::Large)) .child(Label::new( @@ -163,7 +163,7 @@ impl Render for CopilotCodeVerification { _ => div().into_any_element(), }; - v_stack() + v_flex() .id("copilot code verification") .elevation_3(cx) .w_96() diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 844a44c54f8bcf6eeae17ab3e629b4dee6e4a04b..d88a8b7c23b07b7f22cbdc35bfd88c334303e6e4 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -36,7 +36,7 @@ use std::{ }; use theme::ActiveTheme; pub use toolbar_controls::ToolbarControls; -use ui::{h_stack, prelude::*, Icon, IconName, Label}; +use ui::{h_flex, prelude::*, Icon, IconName, Label}; use util::TryFutureExt; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, ItemHandle}, @@ -654,11 +654,11 @@ impl Item for ProjectDiagnosticsEditor { }) .into_any_element() } else { - h_stack() + h_flex() .gap_1() .when(self.summary.error_count > 0, |then| { then.child( - h_stack() + h_flex() .gap_1() .child(Icon::new(IconName::XCircle).color(Color::Error)) .child(Label::new(self.summary.error_count.to_string()).color( @@ -672,7 +672,7 @@ impl Item for ProjectDiagnosticsEditor { }) .when(self.summary.warning_count > 0, |then| { then.child( - h_stack() + h_flex() .gap_1() .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning)) .child(Label::new(self.summary.warning_count.to_string()).color( @@ -796,7 +796,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let message: SharedString = message.into(); Arc::new(move |cx| { let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into(); - h_stack() + h_flex() .id("diagnostic header") .py_2() .pl_10() @@ -805,7 +805,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .justify_between() .gap_2() .child( - h_stack() + h_flex() .gap_3() .map(|stack| { stack.child( @@ -824,7 +824,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { ) }) .child( - h_stack() + h_flex() .gap_1() .child( StyledText::new(message.clone()).with_highlights( @@ -844,7 +844,7 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { ), ) .child( - h_stack() + h_flex() .gap_1() .when_some(diagnostic.source.as_ref(), |stack, source| { stack.child( diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 035b84e1020048cd7c6d2cd107577b7c79786169..462718c0f345268131cd04867d2bff9f48a451a0 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -6,7 +6,7 @@ use gpui::{ }; use language::Diagnostic; use lsp::LanguageServerId; -use ui::{h_stack, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; +use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; use crate::{Deploy, ProjectDiagnosticsEditor}; @@ -23,14 +23,14 @@ pub struct DiagnosticIndicator { impl Render for DiagnosticIndicator { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { - (0, 0) => h_stack().map(|this| { + (0, 0) => h_flex().map(|this| { this.child( Icon::new(IconName::Check) .size(IconSize::Small) .color(Color::Default), ) }), - (0, warning_count) => h_stack() + (0, warning_count) => h_flex() .gap_1() .child( Icon::new(IconName::ExclamationTriangle) @@ -38,7 +38,7 @@ impl Render for DiagnosticIndicator { .color(Color::Warning), ) .child(Label::new(warning_count.to_string()).size(LabelSize::Small)), - (error_count, 0) => h_stack() + (error_count, 0) => h_flex() .gap_1() .child( Icon::new(IconName::XCircle) @@ -46,7 +46,7 @@ impl Render for DiagnosticIndicator { .color(Color::Error), ) .child(Label::new(error_count.to_string()).size(LabelSize::Small)), - (error_count, warning_count) => h_stack() + (error_count, warning_count) => h_flex() .gap_1() .child( Icon::new(IconName::XCircle) @@ -64,7 +64,7 @@ impl Render for DiagnosticIndicator { let status = if !self.in_progress_checks.is_empty() { Some( - h_stack() + h_flex() .gap_2() .child(Icon::new(IconName::ArrowCircle).size(IconSize::Small)) .child( @@ -91,7 +91,7 @@ impl Render for DiagnosticIndicator { None }; - h_stack() + h_flex() .h(rems(1.375)) .gap_2() .child( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 56c5be0fbd69c64824ec9635199732d224a1bd6e..01de8c20b1ea8818bcb353d5f53227b09d2c2b73 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,8 +99,8 @@ use sum_tree::TreeMap; use text::{OffsetUtf16, Rope}; use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; use ui::{ - h_stack, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, - Popover, Tooltip, + h_flex, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, Popover, + Tooltip, }; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; @@ -1264,7 +1264,7 @@ impl CompletionsMenu { None } else { Some( - h_stack().ml_4().child( + h_flex().ml_4().child( Label::new(text.clone()) .size(LabelSize::Small) .color(Color::Muted), @@ -1290,7 +1290,7 @@ impl CompletionsMenu { ) .map(|task| task.detach_and_log_err(cx)); })) - .child(h_stack().overflow_hidden().child(completion_label)) + .child(h_flex().overflow_hidden().child(completion_label)) .end_slot::

(documentation_label), ) }) @@ -9747,7 +9747,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren let group_id: SharedString = cx.block_id.to_string().into(); // TODO: Nate: We should tint the background of the block with the severity color // We need to extend the theme before we can do this - h_stack() + h_flex() .id(cx.block_id) .group(group_id.clone()) .relative() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b2295b634a1a51ed123212c8f438d6f5ebd2135d..895df153406934f9d38dca0d25994baf6f69adb6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -53,7 +53,7 @@ use std::{ use sum_tree::Bias; use theme::{ActiveTheme, PlayerColor}; use ui::prelude::*; -use ui::{h_stack, ButtonLike, ButtonStyle, IconButton, Tooltip}; +use ui::{h_flex, ButtonLike, ButtonStyle, IconButton, Tooltip}; use util::ResultExt; use workspace::item::Item; @@ -2293,7 +2293,7 @@ impl EditorElement { .size_full() .p_1p5() .child( - h_stack() + h_flex() .id("path header block") .py_1p5() .pl_3() @@ -2306,8 +2306,8 @@ impl EditorElement { .justify_between() .hover(|style| style.bg(cx.theme().colors().element_hover)) .child( - h_stack().gap_3().child( - h_stack() + h_flex().gap_3().child( + h_flex() .gap_2() .child( filename @@ -2339,12 +2339,12 @@ impl EditorElement { }), ) } else { - h_stack() + h_flex() .id(("collapsed context", block_id)) .size_full() .gap(gutter_padding) .child( - h_stack() + h_flex() .justify_end() .flex_none() .w(gutter_width - gutter_padding) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index a8583d48afae6abfee508026a65d109f78aafc95..ec9105362901b1b5fdd24206047846ef6ef3af86 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -32,7 +32,7 @@ use std::{ }; use text::Selection; use theme::Theme; -use ui::{h_stack, prelude::*, Label}; +use ui::{h_flex, prelude::*, Label}; use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt}; use workspace::{ item::{BreadcrumbText, FollowEvent, FollowableItemHandle}, @@ -619,7 +619,7 @@ impl Item for Editor { Some(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)) }); - h_stack() + h_flex() .gap_2() .child(Label::new(self.title(cx).to_string()).color(label_color)) .when_some(description, |this, description| { diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 38e06376cb6a46795181a3498a0d048b7a968e39..80722580b7afe9c37de4e1fb7edde97f3515db76 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -422,7 +422,7 @@ impl Render for FeedbackModal { let open_community_repo = cx.listener(|_, _, cx| cx.dispatch_action(Box::new(OpenZedCommunityRepo))); - v_stack() + v_flex() .elevation_3(cx) .key_context("GiveFeedback") .on_action(cx.listener(Self::cancel)) @@ -461,10 +461,10 @@ impl Render for FeedbackModal { .child(self.feedback_editor.clone()), ) .child( - v_stack() + v_flex() .gap_1() .child( - h_stack() + h_flex() .bg(cx.theme().colors().editor_background) .p_2() .border() @@ -483,7 +483,7 @@ impl Render for FeedbackModal { ), ) .child( - h_stack() + h_flex() .justify_between() .gap_1() .child( @@ -495,7 +495,7 @@ impl Render for FeedbackModal { .on_click(open_community_repo), ) .child( - h_stack() + h_flex() .gap_1() .child( Button::new("cancel_feedback", "Cancel") diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index ed3e57ba28a0bfbebbcac1f8aff3482c953be276..022860ea4718dbf39717cc86c631721d5e86621b 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -119,7 +119,7 @@ impl FocusableView for FileFinder { } impl Render for FileFinder { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } @@ -786,7 +786,7 @@ impl PickerDelegate for FileFinderDelegate { .inset(true) .selected(selected) .child( - v_stack() + v_flex() .child(HighlightedLabel::new(file_name, file_name_positions)) .child(HighlightedLabel::new(full_path, full_path_positions)), ), diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index ec58cbdc60556742414cc3692ef79f0658fbc750..b7e3f27fac257bab08ac1e85401cf7cb5a383dfc 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -5,7 +5,7 @@ use gpui::{ }; use text::{Bias, Point}; use theme::ActiveTheme; -use ui::{h_stack, prelude::*, v_stack, Label}; +use ui::{h_flex, prelude::*, v_flex, Label}; use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::ModalView; @@ -160,12 +160,12 @@ impl Render for GoToLine { .on_action(cx.listener(Self::confirm)) .w_96() .child( - v_stack() + v_flex() .px_1() .pt_0p5() .gap_px() .child( - v_stack() + v_flex() .py_0p5() .px_1() .child(div().px_1().py_0p5().child(self.line_editor.clone())), @@ -177,7 +177,7 @@ impl Render for GoToLine { .bg(cx.theme().colors().element_background), ) .child( - h_stack() + h_flex() .justify_between() .px_2() .py_1() diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index 33b2e481263e9fea84e77993668de16f5735726f..00ff809fc4dcd499c6d11ce27726827213f64a67 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -68,7 +68,7 @@ impl LanguageSelector { impl Render for LanguageSelector { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 62aead0857c87e6f88482991b4d7f18dae34ca59..52a1d11e74f2be6cf33123972f5c847d6d9e563e 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -785,7 +785,7 @@ impl Render for LspLogToolbarItemView { { let log_toolbar_view = log_toolbar_view.clone(); move |cx| { - h_stack() + h_flex() .w_full() .justify_between() .child(Label::new(RPC_MESSAGES)) @@ -837,7 +837,7 @@ impl Render for LspLogToolbarItemView { .into() }); - h_stack().size_full().child(lsp_menu).child( + h_flex().size_full().child(lsp_menu).child( div() .child( Button::new("clear_log_button", "Clear").on_click(cx.listener( diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index b49e4eb649ae28fea72ad2d83402653961caf0b8..bfe2b2a03be70272d62a18733fe9b655834ea99b 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -9,7 +9,7 @@ use language::{Buffer, OwnedSyntaxLayerInfo}; use std::{mem, ops::Range}; use theme::ActiveTheme; use tree_sitter::{Node, TreeCursor}; -use ui::{h_stack, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu}; +use ui::{h_flex, popover_menu, ButtonLike, Color, ContextMenu, Label, LabelCommon, PopoverMenu}; use workspace::{ item::{Item, ItemHandle}, SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, @@ -239,7 +239,7 @@ impl SyntaxTreeView { fn render_node(cursor: &TreeCursor, depth: u32, selected: bool, cx: &AppContext) -> Div { let colors = cx.theme().colors(); - let mut row = h_stack(); + let mut row = h_flex(); if let Some(field_name) = cursor.field_name() { row = row.children([Label::new(field_name).color(Color::Info), Label::new(": ")]); } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index a661a693b1f5733dd27218f27ad502ea0d763c78..1f2112003974aeb95ba55202af87ec20e7e04331 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -64,7 +64,7 @@ impl ModalView for OutlineView { impl Render for OutlineView { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index ad7552052055be100bcd3d1cad9e398416387aa6..0af4675c9803505a4848049233fb02b60b308632 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -5,7 +5,7 @@ use gpui::{ View, ViewContext, WindowContext, }; use std::{cmp, sync::Arc}; -use ui::{prelude::*, v_stack, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator}; +use ui::{prelude::*, v_flex, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator}; use workspace::ModalView; pub struct Picker { @@ -236,7 +236,7 @@ impl ModalView for Picker {} impl Render for Picker { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let picker_editor = h_stack() + let picker_editor = h_flex() .overflow_hidden() .flex_none() .h_9() @@ -264,7 +264,7 @@ impl Render for Picker { .child(Divider::horizontal()) .when(self.delegate.match_count() > 0, |el| { el.child( - v_stack() + v_flex() .flex_grow() .py_2() .max_h(self.max_height.unwrap_or(rems(18.).into())) @@ -309,7 +309,7 @@ impl Render for Picker { }) .when(self.delegate.match_count() == 0, |el| { el.child( - v_stack().flex_grow().py_2().child( + v_flex().flex_grow().py_2().child( ListItem::new("empty_state") .inset(true) .spacing(ListItemSpacing::Sparse) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 93789a7206a43d49e67e1c2fb54f3bc9c91ace50..a7b9ad2404d77686ca5743a38d59507fa1c6d671 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -30,7 +30,7 @@ use std::{ sync::Arc, }; use theme::ThemeSettings; -use ui::{prelude::*, v_stack, ContextMenu, Icon, KeyBinding, Label, ListItem}; +use ui::{prelude::*, v_flex, ContextMenu, Icon, KeyBinding, Label, ListItem}; use unicase::UniCase; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1541,7 +1541,7 @@ impl Render for ProjectPanel { .child(menu.clone()) })) } else { - v_stack() + v_flex() .id("empty-project_panel") .size_full() .p_4() @@ -1565,7 +1565,7 @@ impl Render for DraggedProjectEntryView { fn render(&mut self, cx: &mut ViewContext) -> impl Element { let settings = ProjectPanelSettings::get_global(cx); let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); - h_stack() + h_flex() .font(ui_font) .bg(cx.theme().colors().background) .w(self.width) diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 23dfd21b8256574bf122215cd6d34e36d8059f1f..68a0721b4c54386f31176aef44fb6a0733b02524 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -11,7 +11,7 @@ use std::{borrow::Cow, cmp::Reverse, sync::Arc}; use theme::ActiveTheme; use util::ResultExt; use workspace::{ - ui::{v_stack, Color, Label, LabelCommon, LabelLike, ListItem, ListItemSpacing, Selectable}, + ui::{v_flex, Color, Label, LabelCommon, LabelLike, ListItem, ListItemSpacing, Selectable}, Workspace, }; @@ -242,7 +242,7 @@ impl PickerDelegate for ProjectSymbolsDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - v_stack() + v_flex() .child( LabelLike::new().child( StyledText::new(label) diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index cf4941bcec66cdef77f3f4453a6b3eb25d8b1321..865632142a48978f33a446a49fe65c20183cf832 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -93,7 +93,7 @@ impl Render for QuickActionBar { }, ); - h_stack() + h_flex() .id("quick action bar") .gap_2() .children(inlay_hints_button) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index c3b2c21d522ed3efdbf208433e6070b2b49dcf44..6208635e22d969bfa9219d7eb4d1466a35a0999d 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -104,7 +104,7 @@ impl FocusableView for RecentProjects { impl Render for RecentProjects { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .w(rems(self.rem_width)) .child(self.picker.clone()) .on_mouse_down_out(cx.listener(|this, _, cx| { @@ -236,7 +236,7 @@ impl PickerDelegate for RecentProjectsDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - v_stack() + v_flex() .child(highlighted_location.names) .when(self.render_paths, |this| { this.children(highlighted_location.paths) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 9cbe49d99ea65414a89649be17fb3c5dc196cd83..e217a7ab73cd2fd1aaa540f1b56cf13b7ec1c84b 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -21,7 +21,7 @@ use settings::Settings; use std::{any::Any, sync::Arc}; use theme::ThemeSettings; -use ui::{h_stack, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip}; +use ui::{h_flex, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip}; use util::ResultExt; use workspace::{ item::ItemHandle, @@ -186,7 +186,7 @@ impl Render for BufferSearchBar { } else { cx.theme().colors().border }; - h_stack() + h_flex() .w_full() .gap_2() .key_context(key_context) @@ -216,7 +216,7 @@ impl Render for BufferSearchBar { this.on_action(cx.listener(Self::toggle_whole_word)) }) .child( - h_stack() + h_flex() .flex_1() .px_2() .py_1() @@ -243,11 +243,11 @@ impl Render for BufferSearchBar { })), ) .child( - h_stack() + h_flex() .gap_2() .flex_none() .child( - h_stack() + h_flex() .child( ToggleButton::new("search-mode-text", SearchMode::Text.label()) .style(ButtonStyle::Filled) @@ -303,12 +303,12 @@ impl Render for BufferSearchBar { }), ) .child( - h_stack() + h_flex() .gap_0p5() .flex_1() .when(self.replace_enabled, |this| { this.child( - h_stack() + h_flex() .flex_1() // We're giving this a fixed height to match the height of the search input, // which has an icon inside that is increasing its height. @@ -346,7 +346,7 @@ impl Render for BufferSearchBar { }), ) .child( - h_stack() + h_flex() .gap_0p5() .flex_none() .child( diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8897ae4bcfcd2ec2f14266dac81d97e8cf161985..d0679ed0ee2b5c929dca4911c5c2b7f1f679dfb0 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -38,7 +38,7 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, v_stack, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, + h_flex, prelude::*, v_flex, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, Selectable, ToggleButton, Tooltip, }; use util::{paths::PathMatcher, ResultExt as _}; @@ -360,19 +360,19 @@ impl Render for ProjectSearchView { .max_w_96() .child(Label::new(text).size(LabelSize::Small)) }); - v_stack() + v_flex() .flex_1() .size_full() .justify_center() .bg(cx.theme().colors().editor_background) .track_focus(&self.focus_handle) .child( - h_stack() + h_flex() .size_full() .justify_center() - .child(h_stack().flex_1()) - .child(v_stack().child(major_text).children(minor_text)) - .child(h_stack().flex_1()), + .child(h_flex().flex_1()) + .child(v_flex().child(major_text).children(minor_text)) + .child(h_flex().flex_1()), ) } } @@ -431,7 +431,7 @@ impl Item for ProjectSearchView { let tab_name = last_query .filter(|query| !query.is_empty()) .unwrap_or_else(|| "Project search".into()); - h_stack() + h_flex() .gap_2() .child(Icon::new(IconName::MagnifyingGlass).color(if selected { Color::Default @@ -1601,8 +1601,8 @@ impl Render for ProjectSearchBar { let search = search.read(cx); let semantic_is_available = SemanticIndex::enabled(cx); - let query_column = v_stack().child( - h_stack() + let query_column = v_flex().child( + h_flex() .min_w(rems(512. / 16.)) .px_2() .py_1() @@ -1617,7 +1617,7 @@ impl Render for ProjectSearchBar { .child(Icon::new(IconName::MagnifyingGlass)) .child(self.render_text_input(&search.query_editor, cx)) .child( - h_stack() + h_flex() .child( IconButton::new("project-search-filter-button", IconName::Filter) .tooltip(|cx| { @@ -1674,11 +1674,11 @@ impl Render for ProjectSearchBar { ), ); - let mode_column = v_stack().items_start().justify_start().child( - h_stack() + let mode_column = v_flex().items_start().justify_start().child( + h_flex() .gap_2() .child( - h_stack() + h_flex() .child( ToggleButton::new("project-search-text-button", "Text") .style(ButtonStyle::Filled) @@ -1744,7 +1744,7 @@ impl Render for ProjectSearchBar { ), ); let replace_column = if search.replace_enabled { - h_stack() + h_flex() .flex_1() .h_full() .gap_2() @@ -1757,9 +1757,9 @@ impl Render for ProjectSearchBar { .child(self.render_text_input(&search.replacement_editor, cx)) } else { // Fill out the space if we don't have a replacement editor. - h_stack().flex_1() + h_flex().flex_1() }; - let actions_column = h_stack() + let actions_column = h_flex() .when(search.replace_enabled, |this| { this.child( IconButton::new("project-search-replace-next", IconName::ReplaceNext) @@ -1820,7 +1820,7 @@ impl Render for ProjectSearchBar { .tooltip(|cx| Tooltip::for_action("Go to next match", &SelectNextMatch, cx)), ); - v_stack() + v_flex() .key_context(key_context) .flex_grow() .gap_2() @@ -1880,7 +1880,7 @@ impl Render for ProjectSearchBar { }) }) .child( - h_stack() + h_flex() .justify_between() .gap_2() .child(query_column) @@ -1890,12 +1890,12 @@ impl Render for ProjectSearchBar { ) .when(search.filters_enabled, |this| { this.child( - h_stack() + h_flex() .flex_1() .gap_2() .justify_between() .child( - h_stack() + h_flex() .flex_1() .h_full() .px_2() @@ -1921,7 +1921,7 @@ impl Render for ProjectSearchBar { }), ) .child( - h_stack() + h_flex() .flex_1() .h_full() .px_2() diff --git a/crates/story/src/story.rs b/crates/story/src/story.rs index 65bebfc423f3812f28a31c6c42d9d6307189fcf2..f5448831cb168c240d4e8d8c038cd3f02a0bd2dd 100644 --- a/crates/story/src/story.rs +++ b/crates/story/src/story.rs @@ -255,8 +255,8 @@ impl Story { .child(label.into()) } - /// Note: Not ui::v_stack() as the story crate doesn't depend on the ui crate. - pub fn v_stack() -> Div { + /// Note: Not `ui::v_flex` as the `story` crate doesn't depend on the `ui` crate. + pub fn v_flex() -> Div { div().flex().flex_col().gap_1() } } @@ -298,7 +298,7 @@ impl RenderOnce for StoryItem { .gap_4() .w_full() .child( - Story::v_stack() + Story::v_flex() .px_2() .w_1_2() .min_h_px() @@ -319,7 +319,7 @@ impl RenderOnce for StoryItem { }), ) .child( - Story::v_stack() + Story::v_flex() .px_2() .flex_none() .w_1_2() diff --git a/crates/storybook/src/stories/overflow_scroll.rs b/crates/storybook/src/stories/overflow_scroll.rs index 32ab940be5d68fdf31681c5ae787e6a27a466a7f..ee2ea12c00e0dc9c2af38eeaa9592a378102e5d9 100644 --- a/crates/storybook/src/stories/overflow_scroll.rs +++ b/crates/storybook/src/stories/overflow_scroll.rs @@ -11,7 +11,7 @@ impl Render for OverflowScrollStory { .child(Story::title("Overflow Scroll")) .child(Story::label("`overflow_x_scroll`")) .child( - h_stack() + h_flex() .id("overflow_x_scroll") .gap_2() .overflow_x_scroll() @@ -24,7 +24,7 @@ impl Render for OverflowScrollStory { ) .child(Story::label("`overflow_y_scroll`")) .child( - v_stack() + v_flex() .id("overflow_y_scroll") .gap_2() .overflow_y_scroll() diff --git a/crates/storybook/src/stories/text.rs b/crates/storybook/src/stories/text.rs index 1c302cb48fae2d284c9f1667bde221c0ecfd3693..065b5bf795ba89fa1747c40fc1387ff00d4cb9c0 100644 --- a/crates/storybook/src/stories/text.rs +++ b/crates/storybook/src/stories/text.rs @@ -117,7 +117,7 @@ impl Render for TextStory { // type Element = Div; // fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { -// v_stack() +// v_flex() // .bg(blue()) // .child( // div() diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index dee18ea73b55f7d17c9cc742de179ed580f3d596..8954e70e8fc18d3d78775ba4eb56b11ec251c0de 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -13,7 +13,7 @@ use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; use settings::Settings; use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings}; -use ui::{h_stack, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip}; +use ui::{h_flex, ButtonCommon, Clickable, IconButton, IconSize, Selectable, Tooltip}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -68,7 +68,7 @@ impl TerminalPanel { pane.display_nav_history_buttons(false); pane.set_render_tab_bar_buttons(cx, move |pane, cx| { let terminal_panel = terminal_panel.clone(); - h_stack() + h_flex() .gap_2() .child( IconButton::new("plus", IconName::Plus) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 98a04eb5f5598edab5010125da60d6a171994422..b786877608bddf1de48cdb5b0df61123d637869f 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -20,7 +20,7 @@ use terminal::{ Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal, }; use terminal_element::TerminalElement; -use ui::{h_stack, prelude::*, ContextMenu, Icon, IconName, Label}; +use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label}; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, @@ -697,7 +697,7 @@ impl Item for TerminalView { cx: &WindowContext, ) -> AnyElement { let title = self.terminal().read(cx).title(true); - h_stack() + h_flex() .gap_2() .child(Icon::new(IconName::Terminal)) .child(Label::new(title).color(if selected { diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 2bb8c6648cab0ff1ef79f7f79cf5ee85da8af07c..df66c746de6a16fa51dec22086d765e6d2bfa7ff 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -10,7 +10,7 @@ use picker::{Picker, PickerDelegate}; use settings::{update_settings_file, SettingsStore}; use std::sync::Arc; use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; -use ui::{prelude::*, v_stack, ListItem, ListItemSpacing}; +use ui::{prelude::*, v_flex, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView, Workspace}; @@ -70,7 +70,7 @@ impl FocusableView for ThemeSelector { impl Render for ThemeSelector { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index fcc30e633815fd764909c32bb7bbc34b8c5e0624..3ca6d2867222200e44916c3b4adf86e8ecbb9022 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -362,7 +362,7 @@ impl RenderOnce for Button { }; self.base.child( - h_stack() + h_flex() .gap_1() .when(self.icon_position == Some(IconPosition::Start), |this| { this.children(self.icon.map(|icon| { @@ -375,7 +375,7 @@ impl RenderOnce for Button { })) }) .child( - h_stack() + h_flex() .gap_2() .justify_between() .child( diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 2180e0061773f8626292224e8fb3a2b97ab4cc65..4b66c7bbeec3c898cb84f968241e92c3b0467ea7 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -103,7 +103,7 @@ impl RenderOnce for Checkbox { ), }; - h_stack() + h_flex() .id(self.id) // Rather than adding `px_1()` to add some space around the checkbox, // we use a larger parent element to create a slightly larger diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 098c54f33cb98d831c0806a2b4ec9f5b0a18ad07..5c4f110a415b2e1e8ef0ac2d36b79d7d8bc2b4be 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -1,5 +1,5 @@ use crate::{ - h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator, + h_flex, prelude::*, v_flex, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator, ListSubHeader, }; use gpui::{ @@ -234,7 +234,7 @@ impl ContextMenuItem { impl Render for ContextMenu { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { div().elevation_2(cx).flex().flex_row().child( - v_stack() + v_flex() .min_w(px(200.)) .track_focus(&self.focus_handle) .on_mouse_down_out(cx.listener(|this, _, cx| this.cancel(&menu::Cancel, cx))) @@ -277,7 +277,7 @@ impl Render for ContextMenu { let menu = cx.view().downgrade(); let label_element = if let Some(icon) = icon { - h_stack() + h_flex() .gap_1() .child(Label::new(label.clone())) .child(Icon::new(*icon)) @@ -298,7 +298,7 @@ impl Render for ContextMenu { .ok(); }) .child( - h_stack() + h_flex() .w_full() .justify_between() .child(label_element) diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index e0e0583b7cb25e4966c183ae54d9f4742c66935d..d8077f0ffca2d27cf9b8b9660fcef8bfda62173d 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, Icon, IconName, IconSize}; +use crate::{h_flex, prelude::*, Icon, IconName, IconSize}; use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke}; #[derive(IntoElement, Clone)] @@ -12,13 +12,13 @@ pub struct KeyBinding { impl RenderOnce for KeyBinding { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_stack() + h_flex() .flex_none() .gap_2() .children(self.key_binding.keystrokes().iter().map(|keystroke| { let key_icon = Self::icon_for_key(&keystroke); - h_stack() + h_flex() .flex_none() .gap_0p5() .p_0p5() diff --git a/crates/ui/src/components/list/list.rs b/crates/ui/src/components/list/list.rs index 8c657fdd92a4d35891e2cdae30193ac147741907..436f3e034d17a757e9598fff0584d7239d6f4d65 100644 --- a/crates/ui/src/components/list/list.rs +++ b/crates/ui/src/components/list/list.rs @@ -1,7 +1,7 @@ use gpui::AnyElement; use smallvec::SmallVec; -use crate::{prelude::*, v_stack, Label, ListHeader}; +use crate::{prelude::*, v_flex, Label, ListHeader}; #[derive(IntoElement)] pub struct List { @@ -47,7 +47,7 @@ impl ParentElement for List { impl RenderOnce for List { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - v_stack().w_full().py_1().children(self.header).map(|this| { + v_flex().w_full().py_1().children(self.header).map(|this| { match (self.children.is_empty(), self.toggle) { (false, _) => this.children(self.children), (true, Some(false)) => this, diff --git a/crates/ui/src/components/list/list_header.rs b/crates/ui/src/components/list/list_header.rs index 1bed11601512348c7227b7e048468946145ffe41..7d47f4d3934fd7c7b68ae3728b3b76c6abeb8971 100644 --- a/crates/ui/src/components/list/list_header.rs +++ b/crates/ui/src/components/list/list_header.rs @@ -1,4 +1,4 @@ -use crate::{h_stack, prelude::*, Disclosure, Label}; +use crate::{h_flex, prelude::*, Disclosure, Label}; use gpui::{AnyElement, ClickEvent}; #[derive(IntoElement)] @@ -76,7 +76,7 @@ impl Selectable for ListHeader { impl RenderOnce for ListHeader { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_stack() + h_flex() .id(self.label.clone()) .w_full() .relative() @@ -95,7 +95,7 @@ impl RenderOnce for ListHeader { .w_full() .gap_1() .child( - h_stack() + h_flex() .gap_1() .children(self.toggle.map(|is_open| { Disclosure::new("toggle", is_open).on_toggle(self.on_toggle) @@ -109,7 +109,7 @@ impl RenderOnce for ListHeader { .child(Label::new(self.label.clone()).color(Color::Muted)), ), ) - .child(h_stack().children(self.end_slot)) + .child(h_flex().children(self.end_slot)) .when_some(self.end_hover_slot, |this, end_hover_slot| { this.child( div() diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs index d43de18f9317e9f17d303294f1285d2d1c954b42..804e5191abedb4f2b4f6fa79a739023554817f12 100644 --- a/crates/ui/src/components/list/list_item.rs +++ b/crates/ui/src/components/list/list_item.rs @@ -146,7 +146,7 @@ impl ParentElement for ListItem { impl RenderOnce for ListItem { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_stack() + h_flex() .id(self.id) .w_full() .relative() @@ -169,7 +169,7 @@ impl RenderOnce for ListItem { }) }) .child( - h_stack() + h_flex() .id("inner_list_item") .w_full() .relative() @@ -219,9 +219,9 @@ impl RenderOnce for ListItem { .child(Disclosure::new("toggle", is_open).on_toggle(self.on_toggle)) })) .child( - h_stack() + h_flex() // HACK: We need to set *any* width value here in order for this container to size correctly. - // Without this the `h_stack` will overflow the parent `inner_list_item`. + // Without this the `h_flex` will overflow the parent `inner_list_item`. .w_px() .flex_1() .gap_1() @@ -230,7 +230,7 @@ impl RenderOnce for ListItem { ) .when_some(self.end_slot, |this, end_slot| { this.justify_between().child( - h_stack() + h_flex() .when(self.end_hover_slot.is_some(), |this| { this.visible() .group_hover("list_item", |this| this.invisible()) @@ -240,7 +240,7 @@ impl RenderOnce for ListItem { }) .when_some(self.end_hover_slot, |this, end_hover_slot| { this.child( - h_stack() + h_flex() .h_full() .absolute() .right_2() diff --git a/crates/ui/src/components/list/list_sub_header.rs b/crates/ui/src/components/list/list_sub_header.rs index fc9f35e175c0d42a1517fc1227a4befc7dfdb2da..e607dcaaa896d87e36594178166922a4d92c26d5 100644 --- a/crates/ui/src/components/list/list_sub_header.rs +++ b/crates/ui/src/components/list/list_sub_header.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::{h_stack, Icon, IconName, IconSize, Label}; +use crate::{h_flex, Icon, IconName, IconSize, Label}; #[derive(IntoElement)] pub struct ListSubHeader { @@ -25,7 +25,7 @@ impl ListSubHeader { impl RenderOnce for ListSubHeader { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - h_stack().flex_1().w_full().relative().py_1().child( + h_flex().flex_1().w_full().relative().py_1().child( div() .h_6() .when(self.inset, |this| this.px_2()) diff --git a/crates/ui/src/components/popover.rs b/crates/ui/src/components/popover.rs index acab1e2087667092f20035740d868604528a3935..2e0c5bfec87b84f820bf3bdb512053460e90360f 100644 --- a/crates/ui/src/components/popover.rs +++ b/crates/ui/src/components/popover.rs @@ -1,5 +1,5 @@ use crate::prelude::*; -use crate::v_stack; +use crate::v_flex; use gpui::{ div, AnyElement, Element, IntoElement, ParentElement, RenderOnce, Styled, WindowContext, }; @@ -43,10 +43,10 @@ impl RenderOnce for Popover { div() .flex() .gap_1() - .child(v_stack().elevation_2(cx).px_1().children(self.children)) + .child(v_flex().elevation_2(cx).px_1().children(self.children)) .when_some(self.aside, |this, aside| { this.child( - v_stack() + v_flex() .elevation_2(cx) .bg(cx.theme().colors().surface_background) .px_1() diff --git a/crates/ui/src/components/stack.rs b/crates/ui/src/components/stack.rs index 76f08de911e8e1e60a67c1d9311355f62d55914b..74a5e80575bfe0ebe28334f4533f12b91b1fcfb2 100644 --- a/crates/ui/src/components/stack.rs +++ b/crates/ui/src/components/stack.rs @@ -4,12 +4,12 @@ use crate::StyledExt; /// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()` #[track_caller] -pub fn h_stack() -> Div { +pub fn h_flex() -> Div { div().h_flex() } /// Vertically stacks elements. Sets `flex()`, `flex_col()` #[track_caller] -pub fn v_stack() -> Div { +pub fn v_flex() -> Div { div().v_flex() } diff --git a/crates/ui/src/components/stories/checkbox.rs b/crates/ui/src/components/stories/checkbox.rs index a064c673935a31bc74878833aca22eaff611dfde..b4a966b67e3ae287c604068ec3854c5cf8373a49 100644 --- a/crates/ui/src/components/stories/checkbox.rs +++ b/crates/ui/src/components/stories/checkbox.rs @@ -2,7 +2,7 @@ use gpui::{Render, ViewContext}; use story::Story; use crate::prelude::*; -use crate::{h_stack, Checkbox}; +use crate::{h_flex, Checkbox}; pub struct CheckboxStory; @@ -12,7 +12,7 @@ impl Render for CheckboxStory { .child(Story::title_for::()) .child(Story::label("Default")) .child( - h_stack() + h_flex() .p_2() .gap_2() .rounded_md() @@ -27,7 +27,7 @@ impl Render for CheckboxStory { ) .child(Story::label("Disabled")) .child( - h_stack() + h_flex() .p_2() .gap_2() .rounded_md() diff --git a/crates/ui/src/components/stories/list_item.rs b/crates/ui/src/components/stories/list_item.rs index a25b07df849aeb67a185e3984ab11ec341b07045..f2af011db8e8554b07434e699b61c8492040ede8 100644 --- a/crates/ui/src/components/stories/list_item.rs +++ b/crates/ui/src/components/stories/list_item.rs @@ -60,7 +60,7 @@ impl Render for ListItemStory { ListItem::new("with_end_hover_slot") .child("Hello, world!") .end_slot( - h_stack() + h_flex() .gap_2() .child(Avatar::new(SharedUrl::from( "https://avatars.githubusercontent.com/u/1789?v=4", diff --git a/crates/ui/src/components/stories/tab.rs b/crates/ui/src/components/stories/tab.rs index 9c5c694439f74862d93e7c37155a2c340354e95b..541af75ba4762ef60890d1f21e4ca101680b59a1 100644 --- a/crates/ui/src/components/stories/tab.rs +++ b/crates/ui/src/components/stories/tab.rs @@ -13,10 +13,10 @@ impl Render for TabStory { Story::container() .child(Story::title_for::()) .child(Story::label("Default")) - .child(h_stack().child(Tab::new("tab_1").child("Tab 1"))) + .child(h_flex().child(Tab::new("tab_1").child("Tab 1"))) .child(Story::label("With indicator")) .child( - h_stack().child( + h_flex().child( Tab::new("tab_1") .start_slot(Indicator::dot().color(Color::Warning)) .child("Tab 1"), @@ -24,7 +24,7 @@ impl Render for TabStory { ) .child(Story::label("With close button")) .child( - h_stack().child( + h_flex().child( Tab::new("tab_1") .end_slot( IconButton::new("close_button", IconName::Close) @@ -38,13 +38,13 @@ impl Render for TabStory { ) .child(Story::label("List of tabs")) .child( - h_stack() + h_flex() .child(Tab::new("tab_1").child("Tab 1")) .child(Tab::new("tab_2").child("Tab 2")), ) .child(Story::label("List of tabs with first tab selected")) .child( - h_stack() + h_flex() .child( Tab::new("tab_1") .selected(true) @@ -65,7 +65,7 @@ impl Render for TabStory { ) .child(Story::label("List of tabs with last tab selected")) .child( - h_stack() + h_flex() .child( Tab::new("tab_1") .position(TabPosition::First) @@ -90,7 +90,7 @@ impl Render for TabStory { ) .child(Story::label("List of tabs with second tab selected")) .child( - h_stack() + h_flex() .child( Tab::new("tab_1") .position(TabPosition::First) diff --git a/crates/ui/src/components/stories/tab_bar.rs b/crates/ui/src/components/stories/tab_bar.rs index 289ceff9a6f576739daacd02b57e260b295a7ae8..d6d42fa5e0f3c3ee5540bbd6cdbf4c21ec7edda4 100644 --- a/crates/ui/src/components/stories/tab_bar.rs +++ b/crates/ui/src/components/stories/tab_bar.rs @@ -35,7 +35,7 @@ impl Render for TabBarStory { .child(Story::title_for::()) .child(Story::label("Default")) .child( - h_stack().child( + h_flex().child( TabBar::new("tab_bar_1") .start_child( IconButton::new("navigate_backward", IconName::ArrowLeft) diff --git a/crates/ui/src/components/stories/toggle_button.rs b/crates/ui/src/components/stories/toggle_button.rs index 518165345ca8b483f7c299a22aae4cc91566b839..da2a2512c44a7705d817a12a25b6355f7f792c04 100644 --- a/crates/ui/src/components/stories/toggle_button.rs +++ b/crates/ui/src/components/stories/toggle_button.rs @@ -25,7 +25,7 @@ impl Render for ToggleButtonStory { StorySection::new().child( StoryItem::new( "Toggle button group", - h_stack() + h_flex() .child( ToggleButton::new(1, "Apple") .style(ButtonStyle::Filled) @@ -59,7 +59,7 @@ impl Render for ToggleButtonStory { StorySection::new().child( StoryItem::new( "Toggle button group with selection", - h_stack() + h_flex() .child( ToggleButton::new(1, "Apple") .style(ButtonStyle::Filled) diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index 351c851bb9088db8907e7defff71fe21d79ffe75..7538d809ef3a9ead6a3facef51f6cba675a118cc 100644 --- a/crates/ui/src/components/tab.rs +++ b/crates/ui/src/components/tab.rs @@ -135,7 +135,7 @@ impl RenderOnce for Tab { }) .cursor_pointer() .child( - h_stack() + h_flex() .group("") .relative() .h_full() @@ -145,7 +145,7 @@ impl RenderOnce for Tab { // .hover(|style| style.bg(tab_hover_bg)) // .active(|style| style.bg(tab_active_bg)) .child( - h_stack() + h_flex() .w_3() .h_3() .justify_center() @@ -157,7 +157,7 @@ impl RenderOnce for Tab { .children(self.start_slot), ) .child( - h_stack() + h_flex() .w_3() .h_3() .justify_center() diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs index 0a86f1ae0ce52713bde0761340a86c6d8676a0ec..1f7179c393b686f0a7c7c4a6a49179ea2a609f87 100644 --- a/crates/ui/src/components/tab_bar.rs +++ b/crates/ui/src/components/tab_bar.rs @@ -102,7 +102,7 @@ impl RenderOnce for TabBar { .bg(cx.theme().colors().tab_bar_background) .when(!self.start_children.is_empty(), |this| { this.child( - h_stack() + h_flex() .flex_none() .gap_1() .px_1() @@ -129,7 +129,7 @@ impl RenderOnce for TabBar { .border_color(cx.theme().colors().border), ) .child( - h_stack() + h_flex() .id("tabs") .z_index(2) .flex_grow() @@ -142,7 +142,7 @@ impl RenderOnce for TabBar { ) .when(!self.end_children.is_empty(), |this| { this.child( - h_stack() + h_flex() .flex_none() .gap_1() .px_1() diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index 77fd8d6c0b109cac05bd559fce71529436b7a825..f76085daa35ccfa37131b44bca2d83b9532cffb5 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/crates/ui/src/components/tooltip.rs @@ -3,7 +3,7 @@ use settings::Settings; use theme::ThemeSettings; use crate::prelude::*; -use crate::{h_stack, v_stack, Color, KeyBinding, Label, LabelSize, StyledExt}; +use crate::{h_flex, v_flex, Color, KeyBinding, Label, LabelSize, StyledExt}; pub struct Tooltip { title: SharedString, @@ -73,7 +73,7 @@ impl Render for Tooltip { overlay().child( // padding to avoid mouse cursor div().pl_2().pt_2p5().child( - v_stack() + v_flex() .elevation_2(cx) .font(ui_font) .text_ui() @@ -81,7 +81,7 @@ impl Render for Tooltip { .py_1() .px_2() .child( - h_stack() + h_flex() .gap_4() .child(self.title.clone()) .when_some(self.key_binding.clone(), |this, key_binding| { diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index 69d1d0583d70e8176f577ce7d7fa651845df82f6..837d93db2d20017742a618202cd41eb89075ce6d 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -13,7 +13,7 @@ pub use crate::fixed::*; pub use crate::selectable::*; pub use crate::styles::{vh, vw}; pub use crate::visible_on_hover::*; -pub use crate::{h_stack, v_stack}; +pub use crate::{h_flex, v_flex}; pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; pub use crate::{ButtonCommon, Color, StyledExt}; pub use crate::{Headline, HeadlineSize}; diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index 0774c6f5755324de77b16f691ee0dde11ed38f16..67a12a852bdaeeae08f5fbcdc41ab11a91572605 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -9,7 +9,7 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use std::{ops::Not, sync::Arc}; use ui::{ - h_stack, v_stack, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon, + h_flex, v_flex, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon, LabelSize, ListItem, ListItemSpacing, Selectable, }; use util::ResultExt; @@ -65,7 +65,7 @@ impl FocusableView for BranchList { impl Render for BranchList { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .w(rems(self.rem_width)) .child(self.picker.clone()) .on_mouse_down_out(cx.listener(|this, _, cx| { @@ -290,7 +290,7 @@ impl PickerDelegate for BranchListDelegate { } fn render_header(&self, _: &mut ViewContext>) -> Option { let label = if self.last_query.is_empty() { - h_stack() + h_flex() .ml_3() .child(Label::new("Recent branches").size(LabelSize::Small)) } else { @@ -298,7 +298,7 @@ impl PickerDelegate for BranchListDelegate { let suffix = if self.matches.len() == 1 { "" } else { "es" }; Label::new(format!("{} match{}", self.matches.len(), suffix)).size(LabelSize::Small) }); - h_stack() + h_flex() .px_3() .h_full() .justify_between() @@ -313,7 +313,7 @@ impl PickerDelegate for BranchListDelegate { } Some( - h_stack().mr_3().pb_2().child(h_stack().w_full()).child( + h_flex().mr_3().pb_2().child(h_flex().w_full()).child( Button::new("branch-picker-create-branch-button", "Create branch").on_click( cx.listener(|_, _, cx| { cx.spawn(|picker, mut cx| async move { diff --git a/crates/welcome/src/base_keymap_picker.rs b/crates/welcome/src/base_keymap_picker.rs index e22c89cef882d6e1977e2e349f49f5f4d98c14a1..7913e4df37a39140764722c5f9554a247aef55ec 100644 --- a/crates/welcome/src/base_keymap_picker.rs +++ b/crates/welcome/src/base_keymap_picker.rs @@ -62,7 +62,7 @@ impl BaseKeymapSelector { impl Render for BaseKeymapSelector { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - v_stack().w(rems(34.)).child(self.picker.clone()) + v_flex().w(rems(34.)).child(self.picker.clone()) } } diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 76988fadb06b9124f1b197178cb0c89106670f7a..cefedeb73f52d010e9002b644662696fa479abf2 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -60,8 +60,8 @@ pub struct WelcomePage { impl Render for WelcomePage { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { - h_stack().full().track_focus(&self.focus_handle).child( - v_stack() + h_flex().full().track_focus(&self.focus_handle).child( + v_flex() .w_96() .gap_4() .mx_auto() @@ -74,12 +74,12 @@ impl Render for WelcomePage { .mx_auto(), ) .child( - h_stack() + h_flex() .justify_center() .child(Label::new("Code at the speed of thought")), ) .child( - v_stack() + v_flex() .gap_2() .child( Button::new("choose-theme", "Choose a theme") @@ -129,7 +129,7 @@ impl Render for WelcomePage { ), ) .child( - v_stack() + v_flex() .p_3() .gap_2() .bg(cx.theme().colors().elevated_surface_background) @@ -137,7 +137,7 @@ impl Render for WelcomePage { .border_color(cx.theme().colors().border) .rounded_md() .child( - h_stack() + h_flex() .gap_2() .child( Checkbox::new( @@ -163,7 +163,7 @@ impl Render for WelcomePage { .child(Label::new("Enable vim mode")), ) .child( - h_stack() + h_flex() .gap_2() .child( Checkbox::new( @@ -201,7 +201,7 @@ impl Render for WelcomePage { .child(Label::new("Send anonymous usage data")), ) .child( - h_stack() + h_flex() .gap_2() .child( Checkbox::new( diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index faf69f396d7807376f84a97d10e4697f56fb1ab1..4ae408993572e06e97635f8fb579fbee1395238b 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -9,7 +9,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::SettingsStore; use std::sync::Arc; -use ui::{h_stack, ContextMenu, IconButton, Tooltip}; +use ui::{h_flex, ContextMenu, IconButton, Tooltip}; use ui::{prelude::*, right_click_menu}; const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.); @@ -682,7 +682,7 @@ impl Render for PanelButtons { ) }); - h_stack().gap_0p5().children(buttons) + h_flex().gap_0p5().children(buttons) } } diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index 627581c4760c0209de3379d5a2cbf0ead7cbbc62..d940f1d16842a712bb8aaef23281c5e8ab06217f 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -2,7 +2,7 @@ use gpui::{ div, prelude::*, px, AnyView, DismissEvent, FocusHandle, ManagedView, Render, Subscription, View, ViewContext, WindowContext, }; -use ui::{h_stack, v_stack}; +use ui::{h_flex, v_flex}; pub trait ModalView: ManagedView { fn on_before_dismiss(&mut self, _: &mut ViewContext) -> bool { @@ -120,7 +120,7 @@ impl Render for ModalLayer { .left_0() .z_index(169) .child( - v_stack() + v_flex() .h(px(0.0)) .top_20() .flex() @@ -128,7 +128,7 @@ impl Render for ModalLayer { .items_center() .track_focus(&active_modal.focus_handle) .child( - h_stack() + h_flex() .on_mouse_down_out(cx.listener(|this, _, cx| { this.hide_modal(cx); })) diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index cc2450587d7304a8491aa59137cac9ae758da5ee..6e7590c7d3886493f00d2b43728326549e5665f8 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -173,7 +173,7 @@ pub mod simple_message_notification { }; use std::sync::Arc; use ui::prelude::*; - use ui::{h_stack, v_stack, Button, Icon, IconName, Label, StyledExt}; + use ui::{h_flex, v_flex, Button, Icon, IconName, Label, StyledExt}; pub struct MessageNotification { message: SharedString, @@ -218,11 +218,11 @@ pub mod simple_message_notification { impl Render for MessageNotification { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .elevation_3(cx) .p_4() .child( - h_stack() + h_flex() .justify_between() .child(div().max_w_80().child(Label::new(self.message.clone()))) .child( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 90b27e094b612a5f461fe061cb0c2a77693464e4..70cf84811ebff65245e975052b989b809f18b1f1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -35,7 +35,7 @@ use ui::{ prelude::*, right_click_menu, ButtonSize, Color, IconButton, IconButtonShape, IconName, IconSize, Indicator, Label, Tab, TabBar, TabPosition, Tooltip, }; -use ui::{v_stack, ContextMenu}; +use ui::{v_flex, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] @@ -271,7 +271,7 @@ impl Pane { custom_drop_handle: None, can_split: true, render_tab_bar_buttons: Rc::new(move |pane, cx| { - h_stack() + h_flex() .gap_2() .child( IconButton::new("plus", IconName::Plus) @@ -1444,7 +1444,7 @@ impl Pane { .track_scroll(self.tab_bar_scroll_handle.clone()) .when(self.display_nav_history_buttons, |tab_bar| { tab_bar.start_child( - h_stack() + h_flex() .gap_2() .child( IconButton::new("navigate_backward", IconName::ArrowLeft) @@ -1718,7 +1718,7 @@ impl FocusableView for Pane { impl Render for Pane { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - v_stack() + v_flex() .key_context("Pane") .track_focus(&self.focus_handle) .size_full() diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 5b1ca6477ee99b47ad7abc08e64e0421444c5dfd..6fb156c22f8846857494ea2a52d53c638b0f3ab3 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -12,7 +12,7 @@ use gpui::{ WindowContext, }; use std::sync::{Arc, Weak}; -use ui::{h_stack, prelude::*, Icon, IconName, Label}; +use ui::{h_flex, prelude::*, Icon, IconName, Label}; pub enum Event { Close, @@ -98,7 +98,7 @@ impl Item for SharedScreen { selected: bool, _: &WindowContext<'_>, ) -> gpui::AnyElement { - h_stack() + h_flex() .gap_1() .child(Icon::new(IconName::Screen)) .child( diff --git a/crates/workspace/src/status_bar.rs b/crates/workspace/src/status_bar.rs index bfa1a8f8ba0f39e382b02aa9735720719fdcfbfb..34a1412533bf14829e193df680085bc50c1166c2 100644 --- a/crates/workspace/src/status_bar.rs +++ b/crates/workspace/src/status_bar.rs @@ -4,7 +4,7 @@ use gpui::{ WindowContext, }; use std::any::TypeId; -use ui::{h_stack, prelude::*}; +use ui::{h_flex, prelude::*}; use util::ResultExt; pub trait StatusItemView: Render { @@ -50,14 +50,14 @@ impl Render for StatusBar { impl StatusBar { fn render_left_tools(&self, _: &mut ViewContext) -> impl IntoElement { - h_stack() + h_flex() .items_center() .gap_2() .children(self.left_items.iter().map(|item| item.to_any())) } fn render_right_tools(&self, _: &mut ViewContext) -> impl IntoElement { - h_stack() + h_flex() .items_center() .gap_2() .children(self.right_items.iter().rev().map(|item| item.to_any())) diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index cc072b08b9eac0cf13039dd45216483018d5701a..3d5df3294e1fc84faab10c69c46c7ce424682aad 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -4,7 +4,7 @@ use gpui::{ WindowContext, }; use ui::prelude::*; -use ui::{h_stack, v_stack}; +use ui::{h_flex, v_flex}; pub enum ToolbarItemEvent { ChangeLocation(ToolbarItemLocation), @@ -103,18 +103,18 @@ impl Render for Toolbar { let has_left_items = self.left_items().count() > 0; let has_right_items = self.right_items().count() > 0; - v_stack() + v_flex() .p_2() .when(has_left_items || has_right_items, |this| this.gap_2()) .border_b() .border_color(cx.theme().colors().border_variant) .bg(cx.theme().colors().toolbar_background) .child( - h_stack() + h_flex() .justify_between() .when(has_left_items, |this| { this.child( - h_stack() + h_flex() .flex_1() .justify_start() .children(self.left_items().map(|item| item.to_any())), @@ -122,7 +122,7 @@ impl Render for Toolbar { }) .when(has_right_items, |this| { this.child( - h_stack() + h_flex() .flex_1() .justify_end() .children(self.right_items().map(|item| item.to_any())), From b9be2147e8846d7abc630b54c1300cd1f75a4c57 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Mon, 15 Jan 2024 18:11:01 +0100 Subject: [PATCH 193/334] Fix unaligned close button in tab bar Co-authored-by: Marshall --- crates/assistant/src/assistant_panel.rs | 2 +- crates/collab_ui/src/chat_panel.rs | 2 +- crates/collab_ui/src/notification_panel.rs | 2 +- crates/ui/src/components/tab.rs | 8 +++++--- crates/ui/src/components/tab_bar.rs | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 7ae4955483f42358f1eeb67c5fad5b330d228204..df3dc3754f66aff8d83a6fcd3b92edd38c7c4e45 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1119,7 +1119,7 @@ impl Render for AssistantPanel { ) .children(self.active_editor().map(|editor| { h_flex() - .h(rems(Tab::HEIGHT_IN_REMS)) + .h(rems(Tab::CONTAINER_HEIGHT_IN_REMS)) .flex_1() .px_2() .child(Label::new(editor.read(cx).title(cx)).into_element()) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index cc271a5f5e13bec9d083d65d96974decc71967d6..921e3388a65637fad51f9a093f8538500965923a 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -523,7 +523,7 @@ impl Render for ChatPanel { TabBar::new("chat_header").child( h_flex() .w_full() - .h(rems(ui::Tab::HEIGHT_IN_REMS)) + .h(rems(ui::Tab::CONTAINER_HEIGHT_IN_REMS)) .px_2() .child(Label::new( self.active_chat diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 72c7d4fe6980a856b6d5a0745d86772c87feae48..b30f8d15f035b5bc49e08449d20efd21b0e5b8c9 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -549,7 +549,7 @@ impl Render for NotificationPanel { .px_2() .py_1() // Match the height of the tab bar so they line up. - .h(rems(ui::Tab::HEIGHT_IN_REMS)) + .h(rems(ui::Tab::CONTAINER_HEIGHT_IN_REMS)) .border_b_1() .border_color(cx.theme().colors().border) .child(Label::new("Notifications")) diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index 7538d809ef3a9ead6a3facef51f6cba675a118cc..ade939fdaabcddfcdc6ac0a22b0222ff4f2b4170 100644 --- a/crates/ui/src/components/tab.rs +++ b/crates/ui/src/components/tab.rs @@ -48,7 +48,9 @@ impl Tab { } } - pub const HEIGHT_IN_REMS: f32 = 30. / 16.; + pub const CONTAINER_HEIGHT_IN_REMS: f32 = 29. / 16.; + + const CONTENT_HEIGHT_IN_REMS: f32 = 28. / 16.; pub fn position(mut self, position: TabPosition) -> Self { self.position = position; @@ -111,7 +113,7 @@ impl RenderOnce for Tab { }; self.div - .h(rems(Self::HEIGHT_IN_REMS)) + .h(rems(Self::CONTAINER_HEIGHT_IN_REMS)) .bg(tab_bg) .border_color(cx.theme().colors().border) .map(|this| match self.position { @@ -138,7 +140,7 @@ impl RenderOnce for Tab { h_flex() .group("") .relative() - .h_full() + .h(rems(Self::CONTENT_HEIGHT_IN_REMS)) .px_5() .gap_1() .text_color(text_color) diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs index 1f7179c393b686f0a7c7c4a6a49179ea2a609f87..adee8389e47a8db3f343e01cc9d81a9c1d08ead4 100644 --- a/crates/ui/src/components/tab_bar.rs +++ b/crates/ui/src/components/tab_bar.rs @@ -90,7 +90,7 @@ impl ParentElement for TabBar { impl RenderOnce for TabBar { fn render(self, cx: &mut WindowContext) -> impl IntoElement { - const HEIGHT_IN_REMS: f32 = 30. / 16.; + const HEIGHT_IN_REMS: f32 = 29. / 16.; div() .id(self.id) From 69bbcba99a627d3d3067e20a164367d9bf58129f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 19:19:27 +0100 Subject: [PATCH 194/334] Preserve tooltips requested by cached views Co-Authored-By: Nathan Co-Authored-By: Max --- crates/gpui/src/app.rs | 6 ++-- crates/gpui/src/elements/div.rs | 4 +-- crates/gpui/src/key_dispatch.rs | 2 +- crates/gpui/src/window.rs | 56 +++++++++++++++++++++++++-------- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 108ad28d24a16191b6a419d09b9d56bf212c4050..17f92efb581f3aed55becda1100316f041bdd308 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -196,7 +196,6 @@ pub struct AppContext { pending_updates: usize, pub(crate) actions: Rc, pub(crate) active_drag: Option, - pub(crate) active_tooltip: Option, pub(crate) next_frame_callbacks: FxHashMap>, pub(crate) frame_consumers: FxHashMap>, pub(crate) background_executor: BackgroundExecutor, @@ -258,7 +257,6 @@ impl AppContext { flushing_effects: false, pending_updates: 0, active_drag: None, - active_tooltip: None, next_frame_callbacks: FxHashMap::default(), frame_consumers: FxHashMap::default(), background_executor: executor, @@ -1268,8 +1266,10 @@ pub struct AnyDrag { pub cursor_offset: Point, } +/// Contains state associated with a tooltip. You'll only need this struct if you're implementing +/// tooltip behavior on a custom element. Otherwise, use [Div::tooltip]. #[derive(Clone)] -pub(crate) struct AnyTooltip { +pub struct AnyTooltip { pub view: AnyView, pub cursor_offset: Point, } diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 082f88b8e6775382993922cc6fc72e4f14a2f13d..74000da0512ffac35e4cf87c122bdecf08d67aed 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1433,8 +1433,8 @@ impl Interactivity { .borrow() .as_ref() { - if active_tooltip.tooltip.is_some() { - cx.active_tooltip = active_tooltip.tooltip.clone() + if let Some(tooltip) = active_tooltip.tooltip.clone() { + cx.set_tooltip(tooltip); } } } diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 06d502d7780c9c59dc180d3508d7e4074009dbb6..85a67168e5e1d89588b6d58088a8f381a195e0eb 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -112,7 +112,7 @@ impl DispatchTree { target.action_listeners = mem::take(&mut source.action_listeners); } - pub fn graft(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { + pub fn reuse_view(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> { let view_source_node_id = source .view_node_ids .get(&view_id) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8e38992251f5da196038f2718060d4f12a279496..2a88a4f3976646845589b5127e90c78bde6eb8c0 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1,10 +1,10 @@ #![deny(missing_docs)] use crate::{ - px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, - AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, - DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, - EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, + px, size, transparent_black, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Arena, + AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, + DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, + Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, @@ -300,6 +300,11 @@ struct RequestedInputHandler { handler: Option>, } +struct TooltipRequest { + view_id: EntityId, + tooltip: AnyTooltip, +} + pub(crate) struct Frame { focus: Option, window_active: bool, @@ -313,6 +318,7 @@ pub(crate) struct Frame { content_mask_stack: Vec>, element_offset_stack: Vec>, requested_input_handler: Option, + tooltip_request: Option, cursor_styles: FxHashMap, requested_cursor_style: Option, pub(crate) view_stack: Vec, @@ -328,12 +334,13 @@ impl Frame { mouse_listeners: FxHashMap::default(), dispatch_tree, scene: Scene::default(), + depth_map: Vec::new(), z_index_stack: StackingOrder::default(), next_stacking_order_id: 0, - depth_map: Vec::new(), content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), requested_input_handler: None, + tooltip_request: None, cursor_styles: FxHashMap::default(), requested_cursor_style: None, view_stack: Vec::new(), @@ -350,6 +357,7 @@ impl Frame { self.reused_views.clear(); self.scene.clear(); self.requested_input_handler.take(); + self.tooltip_request.take(); self.cursor_styles.clear(); self.requested_cursor_style.take(); debug_assert_eq!(self.view_stack.len(), 0); @@ -1052,6 +1060,12 @@ impl<'a> WindowContext<'a> { self.window.next_frame.requested_cursor_style = Some(style); } + /// Set a tooltip to be rendered for the upcoming frame + pub fn set_tooltip(&mut self, tooltip: AnyTooltip) { + let view_id = self.parent_view_id(); + self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip }); + } + /// Called during painting to track which z-index is on top at each pixel position pub fn add_opaque_layer(&mut self, bounds: Bounds) { let stacking_order = self.window.next_frame.z_index_stack.clone(); @@ -1432,12 +1446,11 @@ impl<'a> WindowContext<'a> { .window .next_frame .dispatch_tree - .graft(view_id, &mut self.window.rendered_frame.dispatch_tree); + .reuse_view(view_id, &mut self.window.rendered_frame.dispatch_tree); for view_id in grafted_view_ids { assert!(self.window.next_frame.reused_views.insert(view_id)); - // Reuse the previous input handler if it was associated with one of - // the views grafted from the tree in the previous frame. + // Reuse the previous input handler requested during painting of the reused view. if self .window .rendered_frame @@ -1449,6 +1462,19 @@ impl<'a> WindowContext<'a> { self.window.rendered_frame.requested_input_handler.take(); } + // Reuse the tooltip previously requested during painting of the reused view. + if self + .window + .rendered_frame + .tooltip_request + .as_ref() + .map_or(false, |requested| requested.view_id == view_id) + { + self.window.next_frame.tooltip_request = + self.window.rendered_frame.tooltip_request.take(); + } + + // Reuse the cursor styles previously requested during painting of the reused view. if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) { self.window.next_frame.cursor_styles.insert(view_id, style); self.window.next_frame.requested_cursor_style = Some(style); @@ -1498,13 +1524,16 @@ impl<'a> WindowContext<'a> { active_drag.view.draw(offset, available_space, cx); }); self.active_drag = Some(active_drag); - } else if let Some(active_tooltip) = self.app.active_tooltip.take() { + } else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() { self.with_z_index(1, |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); - active_tooltip - .view - .draw(active_tooltip.cursor_offset, available_space, cx); + tooltip_request.tooltip.view.draw( + tooltip_request.tooltip.cursor_offset, + available_space, + cx, + ); }); + self.window.next_frame.tooltip_request = Some(tooltip_request); } self.window.dirty_views.clear(); @@ -2145,7 +2174,8 @@ impl<'a> WindowContext<'a> { /// Set an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the /// platform to receive textual input with proper integration with concerns such - /// as IME interactions. + /// as IME interactions. This handler will be active for the upcoming frame until the following frame is + /// rendered. /// /// [element_input_handler]: crate::ElementInputHandler pub fn handle_input( From a56265e6073f35cba7b7a3add5e4c8ecabf0e32a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 19:27:18 +0100 Subject: [PATCH 195/334] Avoid retrieving layout bounds inside of right click menu event handler Co-Authored-By: Nathan Co-Authored-By: Max --- crates/ui/src/components/right_click_menu.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 2404368f250db3329613e0337c577c3a9512076f..55cdd93a5bee9d4be4c3f293a262b868b6b7825a 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -134,6 +134,7 @@ impl Element for RightClickMenu { let position = element_state.position.clone(); let attach = self.attach.clone(); let child_layout_id = element_state.child_layout_id.clone(); + let child_bounds = cx.layout_bounds(child_layout_id.unwrap()); cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { if phase == DispatchPhase::Bubble @@ -161,9 +162,7 @@ impl Element for RightClickMenu { *menu.borrow_mut() = Some(new_menu); *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { - attach - .unwrap() - .corner(cx.layout_bounds(child_layout_id.unwrap())) + attach.unwrap().corner(child_bounds) } else { cx.mouse_position() }; From e60117dc541cd5d191c8ac00e01a0a50894fdc3b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Jan 2024 19:46:34 +0100 Subject: [PATCH 196/334] Avoid panicking when closing a dragged tab Co-Authored-By: Max Co-Authored-By: Nathan --- crates/workspace/src/pane.rs | 9 ++++----- crates/workspace/src/workspace.rs | 13 ++++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 70cf84811ebff65245e975052b989b809f18b1f1..655acc29c004e5750a7d949ab1f01088ee04ad58 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -219,8 +219,8 @@ pub struct NavigationEntry { #[derive(Clone)] pub struct DraggedTab { pub pane: View, + pub item: Box, pub ix: usize, - pub item_id: EntityId, pub detail: usize, pub is_active: bool, } @@ -1310,9 +1310,9 @@ impl Pane { ) .on_drag( DraggedTab { + item: item.boxed_clone(), pane: cx.view().clone(), detail, - item_id, is_active, ix, }, @@ -1603,7 +1603,7 @@ impl Pane { } let mut to_pane = cx.view().clone(); let split_direction = self.drag_split_direction; - let item_id = dragged_tab.item_id; + let item_id = dragged_tab.item.item_id(); let from_pane = dragged_tab.pane.clone(); self.workspace .update(cx, |_, cx| { @@ -2603,8 +2603,7 @@ mod tests { impl Render for DraggedTab { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone(); - let item = &self.pane.read(cx).items[self.ix]; - let label = item.tab_content(Some(self.detail), false, cx); + let label = self.item.tab_content(Some(self.detail), false, cx); Tab::new("") .selected(self.is_active) .child(label) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index efd2c52989edb51cff2559382f0ec62a2ce2702e..0e7a635cd22bca4f86a4467d8736571f5ed9053d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2250,17 +2250,16 @@ impl Workspace { destination_index: usize, cx: &mut ViewContext, ) { - let item_to_move = source + let Some((item_ix, item_handle)) = source .read(cx) .items() .enumerate() - .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move); - - if item_to_move.is_none() { - log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop"); + .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move) + else { + // Tab was closed during drag return; - } - let (item_ix, item_handle) = item_to_move.unwrap(); + }; + let item_handle = item_handle.clone(); if source != destination { From 92add99260ffa1d35f4994dacb9a58f5baa3f8e4 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 15 Jan 2024 22:05:49 +0200 Subject: [PATCH 197/334] Add LSP logs into the end of the editor, not after its caret Also prevent tabs from being added in readonly editors --- crates/editor/src/editor.rs | 2 +- crates/language_tools/src/lsp_log.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 01de8c20b1ea8818bcb353d5f53227b09d2c2b73..288d25f9cd22e6245d3d743db8ddff1d1433a335 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4513,7 +4513,7 @@ impl Editor { } pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { - if self.move_to_next_snippet_tabstop(cx) { + if self.move_to_next_snippet_tabstop(cx) || self.read_only(cx) { return; } diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 52a1d11e74f2be6cf33123972f5c847d6d9e563e..0720c53cbc57b36c10e4d853716ec84daaf0a0d8 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -405,8 +405,14 @@ impl LspLogView { { log_view.editor.update(cx, |editor, cx| { editor.set_read_only(false); - editor.handle_input(entry.trim(), cx); - editor.handle_input("\n", cx); + let last_point = editor.buffer().read(cx).len(cx); + editor.edit( + vec![ + (last_point..last_point, entry.trim()), + (last_point..last_point, "\n"), + ], + cx, + ); editor.set_read_only(true); }); } From 97047ffaca57bab47b64ea844d5de79be12c01f0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sun, 14 Jan 2024 13:58:12 -0700 Subject: [PATCH 198/334] Enable Channels for everyone --- crates/collab_ui/src/chat_panel.rs | 9 +- crates/collab_ui/src/collab_panel.rs | 196 +++++++++++----------- crates/collab_ui/src/collab_ui.rs | 5 - crates/feature_flags/src/feature_flags.rs | 6 - 4 files changed, 98 insertions(+), 118 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index c919ceac9fd679f734e3a72b2596af72d0191b56..cbcf8ffa0c61014be54aa14e2b1f8b314ba4e897 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -1,4 +1,4 @@ -use crate::{collab_panel, is_channels_feature_enabled, ChatPanelSettings}; +use crate::{collab_panel, ChatPanelSettings}; use anyhow::Result; use call::{room, ActiveCall}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; @@ -630,9 +630,6 @@ impl Panel for ChatPanel { self.active = active; if active { self.acknowledge_last_message(cx); - if !is_channels_feature_enabled(cx) { - cx.emit(PanelEvent::Close); - } } } @@ -641,10 +638,6 @@ impl Panel for ChatPanel { } fn icon(&self, cx: &WindowContext) -> Option { - if !is_channels_feature_enabled(cx) { - return None; - } - Some(ui::IconName::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index c9e1ae1bb8a9c28afeac1434e53cab55c1fc0041..9ff8e9da16b36b14af0bb144901a3fcd92007ad9 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -12,7 +12,6 @@ use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; -use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext, @@ -265,10 +264,6 @@ impl CollabPanel { })); this.subscriptions .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx))); - this.subscriptions - .push(cx.observe_flag::(move |_, this, cx| { - this.update_entries(true, cx) - })); this.subscriptions.push(cx.subscribe( &this.channel_store, |this, _channel_store, e, cx| match e { @@ -504,115 +499,118 @@ impl CollabPanel { let mut request_entries = Vec::new(); - if cx.has_flag::() { - self.entries.push(ListEntry::Header(Section::Channels)); + self.entries.push(ListEntry::Header(Section::Channels)); - if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_store.ordered_channels().enumerate().map( - |(ix, (_, channel))| StringMatchCandidate { + if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() { + self.match_candidates.clear(); + self.match_candidates + .extend( + channel_store + .ordered_channels() + .enumerate() + .map(|(ix, (_, channel))| StringMatchCandidate { id: ix, string: channel.name.clone().into(), char_bag: channel.name.chars().collect(), - }, - )); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - if let Some(state) = &self.channel_editing_state { - if matches!(state, ChannelEditingState::Create { location: None, .. }) { - self.entries.push(ListEntry::ChannelEditor { depth: 0 }); - } + }), + ); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + if let Some(state) = &self.channel_editing_state { + if matches!(state, ChannelEditingState::Create { location: None, .. }) { + self.entries.push(ListEntry::ChannelEditor { depth: 0 }); } - let mut collapse_depth = None; - for mat in matches { - let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); - let depth = channel.parent_path.len(); - - if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + } + let mut collapse_depth = None; + for mat in matches { + let channel = channel_store.channel_at_index(mat.candidate_id).unwrap(); + let depth = channel.parent_path.len(); + + if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + collapse_depth = Some(depth); + } else if let Some(collapsed_depth) = collapse_depth { + if depth > collapsed_depth { + continue; + } + if self.is_channel_collapsed(channel.id) { collapse_depth = Some(depth); - } else if let Some(collapsed_depth) = collapse_depth { - if depth > collapsed_depth { - continue; - } - if self.is_channel_collapsed(channel.id) { - collapse_depth = Some(depth); - } else { - collapse_depth = None; - } + } else { + collapse_depth = None; } + } - let has_children = channel_store - .channel_at_index(mat.candidate_id + 1) - .map_or(false, |next_channel| { - next_channel.parent_path.ends_with(&[channel.id]) - }); + let has_children = channel_store + .channel_at_index(mat.candidate_id + 1) + .map_or(false, |next_channel| { + next_channel.parent_path.ends_with(&[channel.id]) + }); - match &self.channel_editing_state { - Some(ChannelEditingState::Create { - location: parent_id, - .. - }) if *parent_id == Some(channel.id) => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children: false, - }); - self.entries - .push(ListEntry::ChannelEditor { depth: depth + 1 }); - } - Some(ChannelEditingState::Rename { - location: parent_id, - .. - }) if parent_id == &channel.id => { - self.entries.push(ListEntry::ChannelEditor { depth }); - } - _ => { - self.entries.push(ListEntry::Channel { - channel: channel.clone(), - depth, - has_children, - }); - } + match &self.channel_editing_state { + Some(ChannelEditingState::Create { + location: parent_id, + .. + }) if *parent_id == Some(channel.id) => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children: false, + }); + self.entries + .push(ListEntry::ChannelEditor { depth: depth + 1 }); + } + Some(ChannelEditingState::Rename { + location: parent_id, + .. + }) if parent_id == &channel.id => { + self.entries.push(ListEntry::ChannelEditor { depth }); + } + _ => { + self.entries.push(ListEntry::Channel { + channel: channel.clone(), + depth, + has_children, + }); } } } + } - let channel_invites = channel_store.channel_invitations(); - if !channel_invites.is_empty() { - self.match_candidates.clear(); - self.match_candidates - .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { - StringMatchCandidate { - id: ix, - string: channel.name.clone().into(), - char_bag: channel.name.chars().collect(), - } - })); - let matches = executor.block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - executor.clone(), - )); - request_entries.extend(matches.iter().map(|mat| { - ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone()) + let channel_invites = channel_store.channel_invitations(); + if !channel_invites.is_empty() { + self.match_candidates.clear(); + self.match_candidates + .extend(channel_invites.iter().enumerate().map(|(ix, channel)| { + StringMatchCandidate { + id: ix, + string: channel.name.clone().into(), + char_bag: channel.name.chars().collect(), + } })); + let matches = executor.block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + executor.clone(), + )); + request_entries.extend( + matches + .iter() + .map(|mat| ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())), + ); - if !request_entries.is_empty() { - self.entries - .push(ListEntry::Header(Section::ChannelInvites)); - if !self.collapsed_sections.contains(&Section::ChannelInvites) { - self.entries.append(&mut request_entries); - } + if !request_entries.is_empty() { + self.entries + .push(ListEntry::Header(Section::ChannelInvites)); + if !self.collapsed_sections.contains(&Section::ChannelInvites) { + self.entries.append(&mut request_entries); } } } diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 779fd121f8afbfa59eac556c17e7abba605e8eee..6f4929c4d56ae5d806c62b289ed50e8bbab631d3 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -12,7 +12,6 @@ use std::{rc::Rc, sync::Arc}; use call::{report_call_event_for_room, ActiveCall, Room}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; -use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; use gpui::{ actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds, WindowKind, WindowOptions, @@ -126,7 +125,3 @@ fn notification_window_options( display_id: Some(screen.id()), } } - -fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool { - cx.is_staff() || cx.has_flag::() -} diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index 065d06f96d1b1765828329464ddb149da371d63b..ea16ff3f7291fd838be45fd826e7c7504ba914fd 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -16,12 +16,6 @@ pub trait FeatureFlag { const NAME: &'static str; } -pub enum ChannelsAlpha {} - -impl FeatureFlag for ChannelsAlpha { - const NAME: &'static str = "channels_alpha"; -} - pub trait FeatureFlagViewExt { fn observe_flag(&mut self, callback: F) -> Subscription where From 9a70a8947713a571df950614f159e60038d879a9 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 15 Jan 2024 22:23:16 +0100 Subject: [PATCH 199/334] Fix project panel being toggled on workspace startup. (#4059) A sequence of events: Launch Zed -> Quit Zed -> Launch Zed would leave you with a project panel in a a different state on each open (e.g. if it is open on 1st one, 2nd run will have it closed). We were essentially not tracking whether the deserialization took place. Release Notes: - Fixed project panel being toggled on/off on startup due to incorrect tracking of serialization state (solves https://github.com/zed-industries/community/issues/2406) --- crates/project_panel/src/project_panel.rs | 6 ++++++ crates/zed/src/zed.rs | 24 +++++++++++++---------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a7b9ad2404d77686ca5743a38d59507fa1c6d671..4301b6e392df1af4d2f411fc6e51b50564ffb16f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -58,6 +58,7 @@ pub struct ProjectPanel { workspace: WeakView, width: Option, pending_serialization: Task>, + was_deserialized: bool, } #[derive(Copy, Clone, Debug)] @@ -243,6 +244,7 @@ impl ProjectPanel { workspace: workspace.weak_handle(), width: None, pending_serialization: Task::ready(None), + was_deserialized: false, }; this.update_visible_entries(None, cx); @@ -322,6 +324,7 @@ impl ProjectPanel { if let Some(serialized_panel) = serialized_panel { panel.update(cx, |panel, cx| { panel.width = serialized_panel.width; + panel.was_deserialized = true; cx.notify(); }); } @@ -1465,6 +1468,9 @@ impl ProjectPanel { cx.notify(); } } + pub fn was_deserialized(&self) -> bool { + self.was_deserialized + } } impl Render for ProjectPanel { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d7686c425ad6a40663aeb6552e790d00aab50f77..bbe5e781094e33e8a53845d699e50ecd21552871 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -178,7 +178,10 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { )?; workspace_handle.update(&mut cx, |workspace, cx| { - let position = project_panel.read(cx).position(cx); + let (position, was_deserialized) = { + let project_panel = project_panel.read(cx); + (project_panel.position(cx), project_panel.was_deserialized()) + }; workspace.add_panel(project_panel, cx); workspace.add_panel(terminal_panel, cx); workspace.add_panel(assistant_panel, cx); @@ -186,15 +189,16 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { workspace.add_panel(chat_panel, cx); workspace.add_panel(notification_panel, cx); - if workspace - .project() - .read(cx) - .visible_worktrees(cx) - .any(|tree| { - tree.read(cx) - .root_entry() - .map_or(false, |entry| entry.is_dir()) - }) + if !was_deserialized + && workspace + .project() + .read(cx) + .visible_worktrees(cx) + .any(|tree| { + tree.read(cx) + .root_entry() + .map_or(false, |entry| entry.is_dir()) + }) { workspace.toggle_dock(position, cx); } From ba9a9f4f172d0ed803bf266f081c8867a35105ab Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 16:26:04 -0500 Subject: [PATCH 200/334] Add more open events project search diagnostics welcome page --- crates/client/src/telemetry.rs | 48 ++++++++++++------- crates/collab_ui/src/channel_view.rs | 4 ++ crates/diagnostics/src/diagnostics.rs | 4 ++ crates/editor/src/items.rs | 4 ++ crates/language_tools/src/lsp_log.rs | 4 ++ crates/language_tools/src/syntax_tree_view.rs | 4 ++ crates/search/src/project_search.rs | 4 ++ crates/terminal_view/src/terminal_view.rs | 4 ++ crates/welcome/src/welcome.rs | 27 +++++++---- crates/workspace/src/item.rs | 11 +++++ crates/workspace/src/shared_screen.rs | 4 ++ crates/workspace/src/workspace.rs | 12 ++++- crates/zed/src/main.rs | 11 +++-- 13 files changed, 110 insertions(+), 31 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ca717c9d6a6463b0351c0aa312f8aeeefab8c8ac..203840ac1b22dc39574fc4f4063df1d449f82eb5 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -116,7 +116,7 @@ pub enum Event { milliseconds_since_first_event: i64, }, App { - operation: &'static str, + operation: String, milliseconds_since_first_event: i64, }, Setting { @@ -129,6 +129,10 @@ pub enum Event { environment: &'static str, milliseconds_since_first_event: i64, }, + Button { + operation: &'static str, + milliseconds_since_first_event: i64, + }, } #[cfg(debug_assertions)] @@ -219,7 +223,7 @@ impl Telemetry { // TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings #[cfg(not(any(test, feature = "test-support")))] fn shutdown_telemetry(self: &Arc) -> impl Future { - self.report_app_event("close"); + self.report_app_event("close".to_string()); // TODO: close final edit period and make sure it's sent Task::ready(()) } @@ -385,7 +389,7 @@ impl Telemetry { self.report_event(event) } - pub fn report_app_event(self: &Arc, operation: &'static str) { + pub fn report_app_event(self: &Arc, operation: String) { let event = Event::App { operation, milliseconds_since_first_event: self.milliseconds_since_first_event(), @@ -404,20 +408,6 @@ impl Telemetry { self.report_event(event) } - fn milliseconds_since_first_event(&self) -> i64 { - let mut state = self.state.lock(); - match state.first_event_datetime { - Some(first_event_datetime) => { - let now: DateTime = Utc::now(); - now.timestamp_millis() - first_event_datetime.timestamp_millis() - } - None => { - state.first_event_datetime = Some(Utc::now()); - 0 - } - } - } - pub fn log_edit_event(self: &Arc, environment: &'static str) { let mut state = self.state.lock(); let period_data = state.event_coalescer.log_event(environment); @@ -434,6 +424,30 @@ impl Telemetry { } } + pub fn report_button_event(self: &Arc, operation: &'static str) { + let event = Event::Button { + operation, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_event(event) + } + + fn milliseconds_since_first_event(&self) -> i64 { + let mut state = self.state.lock(); + + match state.first_event_datetime { + Some(first_event_datetime) => { + let now: DateTime = Utc::now(); + now.timestamp_millis() - first_event_datetime.timestamp_millis() + } + None => { + state.first_event_datetime = Some(Utc::now()); + 0 + } + } + } + fn report_event(self: &Arc, event: Event) { let mut state = self.state.lock(); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index ce68acfbd83379e77c298152aa95b51280883e0c..033889f771d84bafc13dae5005957096e61cb3c6 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -266,6 +266,10 @@ impl Item for ChannelView { .into_any_element() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext) -> Option> { Some(cx.new_view(|cx| { Self::new( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d88a8b7c23b07b7f22cbdc35bfd88c334303e6e4..ca701e626e7f02284e22f553b507ca55a09524a5 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -688,6 +688,10 @@ impl Item for ProjectDiagnosticsEditor { } } + fn telemetry_event_text(&self) -> Option<&'static str> { + Some("project diagnostics") + } + fn for_each_project_item( &self, cx: &AppContext, diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ec9105362901b1b5fdd24206047846ef6ef3af86..36a48b293788ab22f509f941c7b2a66591614d9a 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -578,6 +578,10 @@ impl Item for Editor { Some(file_path.into()) } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option { let path = path_for_buffer(&self.buffer, detail, true, cx)?; Some(path.to_string_lossy().to_string().into()) diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 0720c53cbc57b36c10e4d853716ec84daaf0a0d8..75b4305b58329a48cd19563fc659be6a27f09af0 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -631,6 +631,10 @@ impl Item for LspLogView { .into_any_element() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn as_searchable(&self, handle: &View) -> Option> { Some(Box::new(handle.clone())) } diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index bfe2b2a03be70272d62a18733fe9b655834ea99b..5acc6bff7fb1472697bd95c363c2914470457ea3 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -397,6 +397,10 @@ impl Item for SyntaxTreeView { .into_any_element() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn clone_on_split( &self, _: workspace::WorkspaceId, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index d0679ed0ee2b5c929dca4911c5c2b7f1f679dfb0..3827b46c67fc29c96c5b361440fb66143c96db1a 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -446,6 +446,10 @@ impl Item for ProjectSearchView { .into_any() } + fn telemetry_event_text(&self) -> Option<&'static str> { + Some("project search") + } + fn for_each_project_item( &self, cx: &AppContext, diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index b786877608bddf1de48cdb5b0df61123d637869f..db4b21627f13a3e050888ca1cf18a06488484b1a 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -708,6 +708,10 @@ impl Item for TerminalView { .into_any() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn clone_on_split( &self, _workspace_id: WorkspaceId, diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index cefedeb73f52d010e9002b644662696fa479abf2..677b57a0225059430b141ca23573d42433f52b77 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -86,7 +86,7 @@ impl Render for WelcomePage { .full_width() .on_click(cx.listener(|this, _, cx| { this.telemetry - .report_app_event("welcome page: change theme"); + .report_app_event("welcome page: change theme".to_string()); this.workspace .update(cx, |workspace, cx| { theme_selector::toggle( @@ -102,8 +102,9 @@ impl Render for WelcomePage { Button::new("choose-keymap", "Choose a keymap") .full_width() .on_click(cx.listener(|this, _, cx| { - this.telemetry - .report_app_event("welcome page: change keymap"); + this.telemetry.report_app_event( + "welcome page: change keymap".to_string(), + ); this.workspace .update(cx, |workspace, cx| { base_keymap_picker::toggle( @@ -119,7 +120,8 @@ impl Render for WelcomePage { Button::new("install-cli", "Install the CLI") .full_width() .on_click(cx.listener(|this, _, cx| { - this.telemetry.report_app_event("welcome page: install cli"); + this.telemetry + .report_app_event("welcome page: install cli".to_string()); cx.app_mut() .spawn( |cx| async move { install_cli::install_cli(&cx).await }, @@ -150,8 +152,9 @@ impl Render for WelcomePage { ) .on_click(cx.listener( move |this, selection, cx| { - this.telemetry - .report_app_event("welcome page: toggle vim"); + this.telemetry.report_app_event( + "welcome page: toggle vim".to_string(), + ); this.update_settings::( selection, cx, @@ -177,7 +180,7 @@ impl Render for WelcomePage { .on_click(cx.listener( move |this, selection, cx| { this.telemetry.report_app_event( - "welcome page: toggle metric telemetry", + "welcome page: toggle metric telemetry".to_string(), ); this.update_settings::( selection, @@ -215,7 +218,8 @@ impl Render for WelcomePage { .on_click(cx.listener( move |this, selection, cx| { this.telemetry.report_app_event( - "welcome page: toggle diagnostic telemetry", + "welcome page: toggle diagnostic telemetry" + .to_string(), ); this.update_settings::( selection, @@ -247,7 +251,8 @@ impl WelcomePage { pub fn new(workspace: &Workspace, cx: &mut ViewContext) -> View { let this = cx.new_view(|cx| { cx.on_release(|this: &mut Self, _, _| { - this.telemetry.report_app_event("welcome page: close"); + this.telemetry + .report_app_event("welcome page: close".to_string()); }) .detach(); @@ -306,6 +311,10 @@ impl Item for WelcomePage { .into_any_element() } + fn telemetry_event_text(&self) -> Option<&'static str> { + Some("welcome page") + } + fn show_toolbar(&self) -> bool { false } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index fb4ed05f6c006d8118304418a76dd6dcffc67576..4f696e4a335040eebc10396262382f60c8f2821f 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -114,6 +114,8 @@ pub trait Item: FocusableView + EventEmitter { } fn tab_content(&self, detail: Option, selected: bool, cx: &WindowContext) -> AnyElement; + fn telemetry_event_text(&self) -> Option<&'static str>; + /// (model id, Item) fn for_each_project_item( &self, @@ -225,6 +227,7 @@ pub trait ItemHandle: 'static + Send { fn tab_tooltip_text(&self, cx: &AppContext) -> Option; fn tab_description(&self, detail: usize, cx: &AppContext) -> Option; fn tab_content(&self, detail: Option, selected: bool, cx: &WindowContext) -> AnyElement; + fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str>; fn dragged_tab_content(&self, detail: Option, cx: &WindowContext) -> AnyElement; fn project_path(&self, cx: &AppContext) -> Option; fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>; @@ -313,6 +316,10 @@ impl ItemHandle for View { self.read(cx).tab_tooltip_text(cx) } + fn telemetry_event_text(&self, cx: &WindowContext) -> Option<&'static str> { + self.read(cx).telemetry_event_text() + } + fn tab_description(&self, detail: usize, cx: &AppContext) -> Option { self.read(cx).tab_description(detail, cx) } @@ -922,6 +929,10 @@ pub mod test { }) } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn tab_content( &self, detail: Option, diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 6fb156c22f8846857494ea2a52d53c638b0f3ab3..bfc16022749c10e5862933dd82d777e3adcbd5f2 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -111,6 +111,10 @@ impl Item for SharedScreen { .into_any() } + fn telemetry_event_text(&self) -> Option<&'static str> { + None + } + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { self.nav_history = Some(history); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index efd2c52989edb51cff2559382f0ec62a2ce2702e..2bbcfa80a26b60f0db7b85199b6259545edc481d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1271,7 +1271,9 @@ impl Workspace { } pub fn open(&mut self, _: &Open, cx: &mut ViewContext) { - self.client().telemetry().report_app_event("open project"); + self.client() + .telemetry() + .report_app_event("open project".to_string()); let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, @@ -1776,6 +1778,14 @@ impl Workspace { } pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { + if let Some(text) = item.telemetry_event_text(cx) { + dbg!("workspace"); + dbg!(&text); + self.client() + .telemetry() + .report_app_event(format!("{}: open", text)); + } + self.active_pane .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx)); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e10c52a175c8633fa3b3bebdb09223f3505587ad..821668001c4fa42f757eea55b5e80361e0456e6d 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -176,10 +176,13 @@ fn main() { telemetry.start(installation_id, session_id, cx); telemetry.report_setting_event("theme", cx.theme().name.to_string()); telemetry.report_setting_event("keymap", BaseKeymap::get_global(cx).to_string()); - telemetry.report_app_event(match existing_installation_id_found { - Some(false) => "first open", - _ => "open", - }); + telemetry.report_app_event( + match existing_installation_id_found { + Some(false) => "first open", + _ => "open", + } + .to_string(), + ); telemetry.flush_events(); let app_state = Arc::new(AppState { From 148c294c02f04b70ce6e463ca957cf1d19623b5d Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 16:26:56 -0500 Subject: [PATCH 201/334] Removed button event --- crates/client/src/telemetry.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 203840ac1b22dc39574fc4f4063df1d449f82eb5..0b363bd336e76ada87a8dd7f396febb80bec14da 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -129,10 +129,6 @@ pub enum Event { environment: &'static str, milliseconds_since_first_event: i64, }, - Button { - operation: &'static str, - milliseconds_since_first_event: i64, - }, } #[cfg(debug_assertions)] From 355d1fca82c8543183ec230537cd009d61466089 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 16:28:28 -0500 Subject: [PATCH 202/334] Remove button event function --- crates/client/src/telemetry.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 0b363bd336e76ada87a8dd7f396febb80bec14da..6276548e4cf3a3cd380dbf9b3a19430fd10922f9 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -420,15 +420,6 @@ impl Telemetry { } } - pub fn report_button_event(self: &Arc, operation: &'static str) { - let event = Event::Button { - operation, - milliseconds_since_first_event: self.milliseconds_since_first_event(), - }; - - self.report_event(event) - } - fn milliseconds_since_first_event(&self) -> i64 { let mut state = self.state.lock(); From 24db41fcdd4f6a23342e9c8338f8ca006ae96911 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 16:29:58 -0500 Subject: [PATCH 203/334] Remove debugs --- crates/workspace/src/workspace.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2bbcfa80a26b60f0db7b85199b6259545edc481d..9b394608aeab133c324c34bb41edfe0bb0a5dcec 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1779,8 +1779,6 @@ impl Workspace { pub fn add_item(&mut self, item: Box, cx: &mut ViewContext) { if let Some(text) = item.telemetry_event_text(cx) { - dbg!("workspace"); - dbg!(&text); self.client() .telemetry() .report_app_event(format!("{}: open", text)); From f0ed80cd8e0820373b6aefc0588fd235adbcfb1c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 15 Jan 2024 14:32:48 -0700 Subject: [PATCH 204/334] Fix fallback font As this is used if you mis-spell "buffer_font_family", it should be monospace. Also treat "Zed Mono" and "Zed Sans" as valid fonts --- crates/gpui/src/text_system.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 3444c05fc115414370e35c85a03d41ab93bc00e2..24438d8c819527a3e8dd869f9e1dac179d9dd618 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -59,14 +59,22 @@ impl TextSystem { fallback_font_stack: smallvec![ // TODO: This is currently Zed-specific. // We should allow GPUI users to provide their own fallback font stack. - font("Zed Sans"), + font("Zed Mono"), font("Helvetica") ], } } pub fn all_font_families(&self) -> Vec { - self.platform_text_system.all_font_families() + let mut families = self.platform_text_system.all_font_families(); + families.append( + &mut self + .fallback_font_stack + .iter() + .map(|font| font.family.to_string()) + .collect(), + ); + families } pub fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { self.platform_text_system.add_fonts(fonts) From 55671eac40465eada4d21fa086141deadebae517 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 15 Jan 2024 23:02:15 +0200 Subject: [PATCH 205/334] Select next/previous word for multiple carets if possible --- crates/editor/src/editor.rs | 198 +++++++++++++++++-------- crates/editor/src/scroll/autoscroll.rs | 4 +- 2 files changed, 141 insertions(+), 61 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 288d25f9cd22e6245d3d743db8ddff1d1433a335..55c31cfa8074734343dd62bb6e138129d77b0d9b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6467,40 +6467,77 @@ impl Editor { } self.select_next_state = Some(select_next_state); - } else if selections.len() == 1 { - let selection = selections.last_mut().unwrap(); - if selection.start == selection.end { - let word_range = movement::surrounding_word( - &display_map, - selection.start.to_display_point(&display_map), - ); - selection.start = word_range.start.to_offset(&display_map, Bias::Left); - selection.end = word_range.end.to_offset(&display_map, Bias::Left); - selection.goal = SelectionGoal::None; - selection.reversed = false; + } else { + let mut only_carets = true; + let mut same_letters_selected = true; + let mut selection_query = None; + + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + if selection.start != selection.end { + only_carets = false; + } - let query = buffer - .text_for_range(selection.start..selection.end) - .collect::(); + if same_letters_selected { + if selection_query.is_none() { + selection_query = + Some(buffer.text_for_range(selection.range()).collect::()); + } - let is_empty = query.is_empty(); - let select_state = SelectNextState { - query: AhoCorasick::new(&[query])?, - wordwise: true, - done: is_empty, - }; - select_next_match_ranges( - self, - selection.start..selection.end, - replace_newest, - autoscroll, - cx, - ); - self.select_next_state = Some(select_state); - } else { - let query = buffer - .text_for_range(selection.start..selection.end) - .collect::(); + if let Some(next_selection) = selections_iter.peek() { + if next_selection.range().len() == selection.range().len() { + let next_query = buffer + .text_for_range(next_selection.range()) + .collect::(); + if Some(next_query) != selection_query { + same_letters_selected = false; + selection_query = None; + } + } else { + same_letters_selected = false; + selection_query = None; + } + } + } + } + + if only_carets { + for selection in &mut selections { + let word_range = movement::surrounding_word( + &display_map, + selection.start.to_display_point(&display_map), + ); + selection.start = word_range.start.to_offset(&display_map, Bias::Left); + selection.end = word_range.end.to_offset(&display_map, Bias::Left); + selection.goal = SelectionGoal::None; + selection.reversed = false; + select_next_match_ranges( + self, + selection.start..selection.end, + replace_newest, + autoscroll, + cx, + ); + } + + if selections.len() == 1 { + let selection = selections + .last() + .expect("ensured that there's only one selection"); + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + let is_empty = query.is_empty(); + let select_state = SelectNextState { + query: AhoCorasick::new(&[query])?, + wordwise: true, + done: is_empty, + }; + self.select_next_state = Some(select_state); + } else { + self.select_next_state = None; + } + } else if let Some(query) = selection_query { self.select_next_state = Some(SelectNextState { query: AhoCorasick::new(&[query])?, wordwise: false, @@ -6548,6 +6585,7 @@ impl Editor { Ok(()) } + // TODO kb test both select_next and select_previous pub fn select_previous( &mut self, action: &SelectPrevious, @@ -6606,37 +6644,79 @@ impl Editor { } self.select_prev_state = Some(select_prev_state); - } else if selections.len() == 1 { - let selection = selections.last_mut().unwrap(); - if selection.start == selection.end { - let word_range = movement::surrounding_word( - &display_map, - selection.start.to_display_point(&display_map), + } else { + let mut only_carets = true; + let mut same_letters_selected = true; + let mut selection_query = None; + + let mut selections_iter = selections.iter().peekable(); + while let Some(selection) = selections_iter.next() { + if selection.start != selection.end { + only_carets = false; + } + + if same_letters_selected { + if selection_query.is_none() { + selection_query = + Some(buffer.text_for_range(selection.range()).collect::()); + } + + if let Some(next_selection) = selections_iter.peek() { + if next_selection.range().len() == selection.range().len() { + let next_query = buffer + .text_for_range(next_selection.range()) + .collect::(); + if Some(next_query) != selection_query { + same_letters_selected = false; + selection_query = None; + } + } else { + same_letters_selected = false; + selection_query = None; + } + } + } + } + + if only_carets { + for selection in &mut selections { + let word_range = movement::surrounding_word( + &display_map, + selection.start.to_display_point(&display_map), + ); + selection.start = word_range.start.to_offset(&display_map, Bias::Left); + selection.end = word_range.end.to_offset(&display_map, Bias::Left); + selection.goal = SelectionGoal::None; + selection.reversed = false; + } + if selections.len() == 1 { + let selection = selections + .last() + .expect("ensured that there's only one selection"); + let query = buffer + .text_for_range(selection.start..selection.end) + .collect::(); + let is_empty = query.is_empty(); + let select_state = SelectNextState { + query: AhoCorasick::new(&[query])?, + wordwise: true, + done: is_empty, + }; + self.select_prev_state = Some(select_state); + } else { + self.select_prev_state = None; + } + + self.unfold_ranges( + selections.iter().map(|s| s.range()).collect::>(), + false, + true, + cx, ); - selection.start = word_range.start.to_offset(&display_map, Bias::Left); - selection.end = word_range.end.to_offset(&display_map, Bias::Left); - selection.goal = SelectionGoal::None; - selection.reversed = false; - - let query = buffer - .text_for_range(selection.start..selection.end) - .collect::(); - let query = query.chars().rev().collect::(); - let select_state = SelectNextState { - query: AhoCorasick::new(&[query])?, - wordwise: true, - done: false, - }; - self.unfold_ranges([selection.start..selection.end], false, true, cx); self.change_selections(Some(Autoscroll::newest()), cx, |s| { s.select(selections); }); - self.select_prev_state = Some(select_state); - } else { - let query = buffer - .text_for_range(selection.start..selection.end) - .collect::(); - let query = query.chars().rev().collect::(); + } else if let Some(query) = selection_query { self.select_prev_state = Some(SelectNextState { query: AhoCorasick::new(&[query])?, wordwise: false, diff --git a/crates/editor/src/scroll/autoscroll.rs b/crates/editor/src/scroll/autoscroll.rs index ba70739942c429e6b5eb11139a395b98db38475a..2a5ac568b79bb9df45489bf5e6b37f37ffcba25b 100644 --- a/crates/editor/src/scroll/autoscroll.rs +++ b/crates/editor/src/scroll/autoscroll.rs @@ -5,7 +5,7 @@ use language::Point; use crate::{display_map::ToDisplayPoint, Editor, EditorMode, LineWithInvisibles}; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Copy)] pub enum Autoscroll { Next, Strategy(AutoscrollStrategy), @@ -25,7 +25,7 @@ impl Autoscroll { } } -#[derive(PartialEq, Eq, Default)] +#[derive(PartialEq, Eq, Default, Clone, Copy)] pub enum AutoscrollStrategy { Fit, Newest, From 8c9f3a7322ee2be43859e6cc7976e08433382c54 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Mon, 15 Jan 2024 17:01:07 -0500 Subject: [PATCH 206/334] init color crate --- Cargo.lock | 76 +++++++++++++++++++++++- crates/color/Cargo.toml | 32 +++++++++++ crates/color/src/color.rs | 118 ++++++++++++++++++++++++++++++++++++++ crates/zed/Cargo.toml | 1 + 4 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 crates/color/Cargo.toml create mode 100644 crates/color/src/color.rs diff --git a/Cargo.lock b/Cargo.lock index 056fab49cd576915fca5443922b0f094591237e1..1b0c6e4bb97ebc9fb8ce9615729fb370a5b18a34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1580,6 +1580,28 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "color" +version = "0.1.0" +dependencies = [ + "anyhow", + "fs", + "indexmap 1.9.3", + "itertools 0.11.0", + "palette", + "parking_lot 0.11.2", + "refineable", + "schemars", + "serde", + "serde_derive", + "serde_json", + "settings", + "story", + "toml 0.5.11", + "util", + "uuid 1.4.1", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -4976,6 +4998,7 @@ dependencies = [ "approx", "fast-srgb8", "palette_derive", + "phf", ] [[package]] @@ -5164,6 +5187,48 @@ dependencies = [ "indexmap 2.0.0", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher 0.3.11", +] + [[package]] name = "picker" version = "0.1.0" @@ -7073,6 +7138,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -7643,7 +7714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff" dependencies = [ "float-cmp", - "siphasher", + "siphasher 0.2.3", ] [[package]] @@ -8857,7 +8928,7 @@ dependencies = [ "roxmltree", "rustybuzz", "simplecss", - "siphasher", + "siphasher 0.2.3", "svgtypes", "ttf-parser 0.12.3", "unicode-bidi", @@ -9649,6 +9720,7 @@ dependencies = [ "client", "collab_ui", "collections", + "color", "command_palette", "copilot", "copilot_ui", diff --git a/crates/color/Cargo.toml b/crates/color/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..c6416f9691b3ca417c1f7426ace5359c199be88b --- /dev/null +++ b/crates/color/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "color" +version = "0.1.0" +edition = "2021" +publish = false + +[features] +default = [] +stories = ["dep:itertools", "dep:story"] + +[lib] +path = "src/color.rs" +doctest = true + +[dependencies] +# TODO: Clean up dependencies +anyhow.workspace = true +fs = { path = "../fs" } +indexmap = "1.6.2" +parking_lot.workspace = true +refineable.workspace = true +schemars.workspace = true +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true +settings = { path = "../settings" } +story = { path = "../story", optional = true } +toml.workspace = true +uuid.workspace = true +util = { path = "../util" } +itertools = { version = "0.11.0", optional = true } +palette = "0.7.3" diff --git a/crates/color/src/color.rs b/crates/color/src/color.rs new file mode 100644 index 0000000000000000000000000000000000000000..77818eb7b8f76c23cc9b05e31402ad04a89aeca3 --- /dev/null +++ b/crates/color/src/color.rs @@ -0,0 +1,118 @@ +//! # Color +//! +//! The `color` crate provides a set utilities for working with colors. It is a wrapper around the [`palette`](https://docs.rs/palette) crate with some additional functionality. +//! +//! It is used to create a manipulate colors when building themes. +//! +//! **Note:** This crate does not depend on `gpui`, so it does not provide any +//! interfaces for converting to `gpui` style colors. + +use palette::{FromColor, Hsl, Hsla, Mix, Srgba, WithAlpha}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum BlendMode { + Multiply, + Screen, + Overlay, + Darken, + Lighten, + Dodge, + Burn, + HardLight, + SoftLight, + Difference, + Exclusion, +} + +/// Creates a new [`palette::Hsl`] color. +pub fn hsl(h: f32, s: f32, l: f32) -> Hsl { + Hsl::new_srgb(h, s, l) +} + +/// Converts a hexadecimal color string to a `palette::Hsla` color. +/// +/// This function supports the following hex formats: +/// `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`. +pub fn hex_to_hsla(s: &str) -> Result { + let hex = s.trim_start_matches('#'); + + // Expand shorthand formats #RGB and #RGBA to #RRGGBB and #RRGGBBAA + let hex = match hex.len() { + 3 => hex + .chars() + .map(|c| c.to_string().repeat(2)) + .collect::(), + 4 => { + let (rgb, alpha) = hex.split_at(3); + let rgb = rgb + .chars() + .map(|c| c.to_string().repeat(2)) + .collect::(); + let alpha = alpha.chars().next().unwrap().to_string().repeat(2); + format!("{}{}", rgb, alpha) + } + 6 => format!("{}ff", hex), // Add alpha if missing + 8 => hex.to_string(), // Already in full format + _ => return Err("Invalid hexadecimal string length".to_string()), + }; + + let hex_val = + u32::from_str_radix(&hex, 16).map_err(|_| format!("Invalid hexadecimal string: {}", s))?; + + let r = ((hex_val >> 24) & 0xFF) as f32 / 255.0; + let g = ((hex_val >> 16) & 0xFF) as f32 / 255.0; + let b = ((hex_val >> 8) & 0xFF) as f32 / 255.0; + let a = (hex_val & 0xFF) as f32 / 255.0; + + let srgba = Srgba::new(r, g, b, a); + let hsl = Hsl::from_color(srgba); + let hsla = Hsla::from(hsl).with_alpha(a); + + Ok(hsla) +} + +/// Mixes two [`palette::Hsl`] colors at the given `mix_ratio`. +pub fn hsl_mix(hsla_1: Hsl, hsla_2: Hsl, mix_ratio: f32) -> Hsl { + hsla_1.mix(hsla_2, mix_ratio).into() +} + +/// Represents a color +/// An interstitial state used to provide a consistent API for colors +/// with additional functionality like color mixing, blending, etc. +/// +/// Does not return [gpui] colors as the `color` crate does not +/// depend on [gpui]. +#[derive(Debug, Copy, Clone)] +pub struct Color { + value: Hsla, +} + +impl Color { + /// Creates a new [`Color`] + pub fn new(hue: f32, saturation: f32, lightness: f32) -> Self { + let hsl = hsl(hue, saturation, lightness); + + Self { value: hsl.into() } + } + + /// Creates a new [`Color`] with an alpha value. + pub fn from_hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self { + Self { + value: Hsla::new(hue, saturation, lightness, alpha), + } + } + + /// Returns the [`palette::Hsla`] value of this color. + pub fn value(&self) -> Hsla { + self.value + } + + /// Mixes this color with another [`palette::Hsl`] color at the given `mix_ratio`. + pub fn mix(&self, other: Hsl, mix_ratio: f32) -> Self { + let mixed = self.value.mix(other.into(), mix_ratio); + + Self { + value: mixed.into(), + } + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 734c225cb1e610c64dab92112accad9634632fee..1e21648408e4855e74cf4f411bc287345bac2bee 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -29,6 +29,7 @@ command_palette = { path = "../command_palette" } # component_test = { path = "../component_test" } client = { path = "../client" } # clock = { path = "../clock" } +color = { path = "../color" } copilot = { path = "../copilot" } copilot_ui = { path = "../copilot_ui" } diagnostics = { path = "../diagnostics" } From e90794d3ecff8923b0f52e50b04cee55bb73712c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 14:03:38 -0800 Subject: [PATCH 207/334] Add and enhance tests for muting/deafening, fix exposed logic errors --- crates/call/src/room.rs | 16 +- .../collab/src/tests/channel_guest_tests.rs | 14 +- crates/collab/src/tests/integration_tests.rs | 180 +++++++++++++++ crates/live_kit_client/src/test.rs | 207 +++++++++++++++--- 4 files changed, 375 insertions(+), 42 deletions(-) diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 0979ad8bb93092ad5e5d75506bff4872f723da08..ed807ef8c5cf4f64207b7c33ac2c8ffc671c1380 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -151,11 +151,14 @@ impl Room { cx.spawn(|this, mut cx| async move { connect.await?; this.update(&mut cx, |this, cx| { - if this.read_only() || this.is_muted() { - Task::ready(Ok(())) - } else { - this.share_microphone(cx) + if !this.read_only() { + if let Some(live_kit) = &this.live_kit { + if !live_kit.muted_by_user && !live_kit.deafened { + return this.share_microphone(cx); + } + } } + Task::ready(Ok(())) })? .await }) @@ -1477,7 +1480,10 @@ impl Room { if let Some(live_kit) = self.live_kit.as_mut() { // When unmuting, undeafen if the user was deafened before. let was_deafened = live_kit.deafened; - if live_kit.muted_by_user || live_kit.deafened { + if live_kit.muted_by_user + || live_kit.deafened + || matches!(live_kit.microphone_track, LocalTrack::None) + { live_kit.muted_by_user = false; live_kit.deafened = false; } else { diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index d5933235926fa1da8c1e8538dedd2a7506504cd8..f3326cd6922b7482f6c9d953a5a57a89676b717d 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -57,7 +57,7 @@ async fn test_channel_guests( }) .await .is_err()); - assert!(room_b.read_with(cx_b, |room, _| !room.is_sharing_mic())); + assert!(room_b.read_with(cx_b, |room, _| room.is_muted())); } #[gpui::test] @@ -104,6 +104,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }); assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); + assert!(room_b.read_with(cx_b, |room, _| room.read_only())); assert!(room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await @@ -127,10 +128,13 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test // project and buffers are now editable assert!(project_b.read_with(cx_b, |project, _| !project.is_read_only())); assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx))); - room_b - .update(cx_b, |room, cx| room.share_microphone(cx)) - .await - .unwrap(); + + // B sees themselves as muted, and can unmute. + assert!(room_b.read_with(cx_b, |room, _| !room.read_only())); + room_b.read_with(cx_b, |room, _| assert!(room.is_muted())); + room_b.update(cx_b, |room, cx| room.toggle_mute(cx)); + cx_a.run_until_parked(); + room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); // B is demoted active_call_a diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index cedc841527ff5c4c40d2630dc9c15166ccec46d8..e68fd10d8d16b29300c3fa658596468df06be880 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -1876,6 +1876,186 @@ fn active_call_events(cx: &mut TestAppContext) -> Rc>> events } +#[gpui::test] +async fn test_mute_deafen( + executor: BackgroundExecutor, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, +) { + let mut server = TestServer::start(executor.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + + server + .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) + .await; + + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + let active_call_c = cx_c.read(ActiveCall::global); + + // User A calls user B, B answers. + active_call_a + .update(cx_a, |call, cx| { + call.invite(client_b.user_id().unwrap(), None, cx) + }) + .await + .unwrap(); + executor.run_until_parked(); + active_call_b + .update(cx_b, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + executor.run_until_parked(); + + let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone()); + let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone()); + + room_a.read_with(cx_a, |room, _| assert!(!room.is_muted())); + room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); + + // Users A and B are both muted. + assert_eq!( + participant_audio_state(&room_a, cx_a), + &[ParticipantAudioState { + user_id: client_b.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![true], + }] + ); + assert_eq!( + participant_audio_state(&room_b, cx_b), + &[ParticipantAudioState { + user_id: client_a.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![true], + }] + ); + + // User A mutes + room_a.update(cx_a, |room, cx| room.toggle_mute(cx)); + executor.run_until_parked(); + + // User A hears user B, but B doesn't hear A. + room_a.read_with(cx_a, |room, _| assert!(room.is_muted())); + room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); + assert_eq!( + participant_audio_state(&room_a, cx_a), + &[ParticipantAudioState { + user_id: client_b.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![true], + }] + ); + assert_eq!( + participant_audio_state(&room_b, cx_b), + &[ParticipantAudioState { + user_id: client_a.user_id().unwrap(), + is_muted: true, + audio_tracks_playing: vec![true], + }] + ); + + // User A deafens + room_a.update(cx_a, |room, cx| room.toggle_deafen(cx)); + executor.run_until_parked(); + + // User A does not hear user B. + room_a.read_with(cx_a, |room, _| assert!(room.is_muted())); + room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); + assert_eq!( + participant_audio_state(&room_a, cx_a), + &[ParticipantAudioState { + user_id: client_b.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![false], + }] + ); + assert_eq!( + participant_audio_state(&room_b, cx_b), + &[ParticipantAudioState { + user_id: client_a.user_id().unwrap(), + is_muted: true, + audio_tracks_playing: vec![true], + }] + ); + + // User B calls user C, C joins. + active_call_b + .update(cx_b, |call, cx| { + call.invite(client_c.user_id().unwrap(), None, cx) + }) + .await + .unwrap(); + executor.run_until_parked(); + active_call_c + .update(cx_c, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + executor.run_until_parked(); + + // User A does not hear users B or C. + assert_eq!( + participant_audio_state(&room_a, cx_a), + &[ + ParticipantAudioState { + user_id: client_b.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![false], + }, + ParticipantAudioState { + user_id: client_c.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![false], + } + ] + ); + assert_eq!( + participant_audio_state(&room_b, cx_b), + &[ + ParticipantAudioState { + user_id: client_a.user_id().unwrap(), + is_muted: true, + audio_tracks_playing: vec![true], + }, + ParticipantAudioState { + user_id: client_c.user_id().unwrap(), + is_muted: false, + audio_tracks_playing: vec![true], + } + ] + ); + + #[derive(PartialEq, Eq, Debug)] + struct ParticipantAudioState { + user_id: u64, + is_muted: bool, + audio_tracks_playing: Vec, + } + + fn participant_audio_state( + room: &Model, + cx: &TestAppContext, + ) -> Vec { + room.read_with(cx, |room, _| { + room.remote_participants() + .iter() + .map(|(user_id, participant)| ParticipantAudioState { + user_id: *user_id, + is_muted: participant.muted, + audio_tracks_playing: participant + .audio_tracks + .values() + .map(|track| track.is_playing()) + .collect(), + }) + .collect::>() + }) + } +} + #[gpui::test(iterations = 10)] async fn test_room_location( executor: BackgroundExecutor, diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 1b7fd20bc257ca5559a37b13ed70b05fe8eb90f7..96ca2b90dcd5de645b927358e2ff2e2779592003 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -1,7 +1,7 @@ use crate::{ConnectionState, RoomUpdate, Sid}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; -use collections::{BTreeMap, HashMap}; +use collections::{BTreeMap, HashMap, HashSet}; use futures::Stream; use gpui::BackgroundExecutor; use live_kit_server::{proto, token}; @@ -13,7 +13,7 @@ use std::{ mem, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, - Arc, + Arc, Weak, }, }; @@ -113,7 +113,25 @@ impl TestServer { .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(Arc::new( + RemoteVideoTrack { + server_track: track.clone(), + }, + ))) + .unwrap(); + } + for track in &room.audio_tracks { + client_room + .0 + .lock() + .updates_tx + .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(&client_room), + }), + Arc::new(RemoteTrackPublication), + )) .unwrap(); } room.client_rooms.insert(identity, client_room); @@ -210,7 +228,7 @@ impl TestServer { } let sid = nanoid::nanoid!(17); - let track = Arc::new(RemoteVideoTrack { + let track = Arc::new(TestServerVideoTrack { sid: sid.clone(), publisher_id: identity.clone(), frames_rx: local_track.frames_rx.clone(), @@ -224,7 +242,11 @@ impl TestServer { .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(Arc::new( + RemoteVideoTrack { + server_track: track.clone(), + }, + ))) .unwrap(); } } @@ -259,10 +281,10 @@ impl TestServer { } let sid = nanoid::nanoid!(17); - let track = Arc::new(RemoteAudioTrack { + let track = Arc::new(TestServerAudioTrack { sid: sid.clone(), publisher_id: identity.clone(), - running: AtomicBool::new(true), + muted: AtomicBool::new(false), }); let publication = Arc::new(RemoteTrackPublication); @@ -276,7 +298,10 @@ impl TestServer { .lock() .updates_tx .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( - track.clone(), + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(&client_room), + }), publication.clone(), )) .unwrap(); @@ -286,37 +311,123 @@ impl TestServer { Ok(sid) } + fn set_track_muted(&self, token: &str, track_sid: &str, muted: bool) -> Result<()> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let room_name = claims.video.room.unwrap(); + let identity = claims.sub.unwrap(); + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + if let Some(track) = room + .audio_tracks + .iter_mut() + .find(|track| track.sid == track_sid) + { + track.muted.store(muted, SeqCst); + for (id, client_room) in room.client_rooms.iter() { + if *id != identity { + client_room + .0 + .lock() + .updates_tx + .try_broadcast(RoomUpdate::RemoteAudioTrackMuteChanged { + track_id: track_sid.to_string(), + muted, + }) + .unwrap(); + } + } + } + Ok(()) + } + + fn is_track_muted(&self, token: &str, track_sid: &str) -> Option { + let claims = live_kit_server::token::validate(&token, &self.secret_key).ok()?; + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms.get_mut(&*room_name)?; + room.audio_tracks.iter().find_map(|track| { + if track.sid == track_sid { + Some(track.muted.load(SeqCst)) + } else { + None + } + }) + } + fn video_tracks(&self, token: String) -> Result>> { let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); + let identity = claims.sub.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; - Ok(room.video_tracks.clone()) + room.client_rooms + .get(identity.as_ref()) + .ok_or_else(|| anyhow!("not a participant in room"))?; + Ok(room + .video_tracks + .iter() + .map(|track| { + Arc::new(RemoteVideoTrack { + server_track: track.clone(), + }) + }) + .collect()) } fn audio_tracks(&self, token: String) -> Result>> { let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); + let identity = claims.sub.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; - Ok(room.audio_tracks.clone()) + let client_room = room + .client_rooms + .get(identity.as_ref()) + .ok_or_else(|| anyhow!("not a participant in room"))?; + Ok(room + .audio_tracks + .iter() + .map(|track| { + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(&client_room), + }) + }) + .collect()) } } #[derive(Default)] struct TestServerRoom { client_rooms: HashMap>, - video_tracks: Vec>, - audio_tracks: Vec>, + video_tracks: Vec>, + audio_tracks: Vec>, participant_permissions: HashMap, } +#[derive(Debug)] +struct TestServerVideoTrack { + sid: Sid, + publisher_id: Sid, + frames_rx: async_broadcast::Receiver, +} + +#[derive(Debug)] +struct TestServerAudioTrack { + sid: Sid, + publisher_id: Sid, + muted: AtomicBool, +} + impl TestServerRoom {} pub struct TestApiClient { @@ -387,6 +498,7 @@ struct RoomState { watch::Receiver, ), display_sources: Vec, + paused_audio_tracks: HashSet, updates_tx: async_broadcast::Sender, updates_rx: async_broadcast::Receiver, } @@ -399,6 +511,7 @@ impl Room { Arc::new(Self(Mutex::new(RoomState { connection: watch::channel_with(ConnectionState::Disconnected), display_sources: Default::default(), + paused_audio_tracks: Default::default(), updates_tx, updates_rx, }))) @@ -444,11 +557,12 @@ impl Room { .publish_video_track(this.token(), track) .await?; Ok(LocalTrackPublication { - muted: Default::default(), + room: Arc::downgrade(&this), sid, }) } } + pub fn publish_audio_track( self: &Arc, track: LocalAudioTrack, @@ -461,7 +575,7 @@ impl Room { .publish_audio_track(this.token(), &track) .await?; Ok(LocalTrackPublication { - muted: Default::default(), + room: Arc::downgrade(&this), sid, }) } @@ -561,20 +675,31 @@ impl Drop for Room { #[derive(Clone)] pub struct LocalTrackPublication { sid: String, - muted: Arc, + room: Weak, } impl LocalTrackPublication { pub fn set_mute(&self, mute: bool) -> impl Future> { - let muted = self.muted.clone(); + let sid = self.sid.clone(); + let room = self.room.clone(); async move { - muted.store(mute, SeqCst); - Ok(()) + if let Some(room) = room.upgrade() { + room.test_server() + .set_track_muted(&room.token(), &sid, mute) + } else { + Err(anyhow!("no such room")) + } } } pub fn is_muted(&self) -> bool { - self.muted.load(SeqCst) + if let Some(room) = self.room.upgrade() { + room.test_server() + .is_track_muted(&room.token(), &self.sid) + .unwrap_or(false) + } else { + false + } } pub fn sid(&self) -> String { @@ -622,47 +747,65 @@ impl LocalAudioTrack { #[derive(Debug)] pub struct RemoteVideoTrack { - sid: Sid, - publisher_id: Sid, - frames_rx: async_broadcast::Receiver, + server_track: Arc, } impl RemoteVideoTrack { pub fn sid(&self) -> &str { - &self.sid + &self.server_track.sid } pub fn publisher_id(&self) -> &str { - &self.publisher_id + &self.server_track.publisher_id } pub fn frames(&self) -> async_broadcast::Receiver { - self.frames_rx.clone() + self.server_track.frames_rx.clone() } } #[derive(Debug)] pub struct RemoteAudioTrack { - sid: Sid, - publisher_id: Sid, - running: AtomicBool, + server_track: Arc, + room: Weak, } impl RemoteAudioTrack { pub fn sid(&self) -> &str { - &self.sid + &self.server_track.sid } pub fn publisher_id(&self) -> &str { - &self.publisher_id + &self.server_track.publisher_id } pub fn start(&self) { - self.running.store(true, SeqCst); + if let Some(room) = self.room.upgrade() { + room.0 + .lock() + .paused_audio_tracks + .remove(&self.server_track.sid); + } } pub fn stop(&self) { - self.running.store(false, SeqCst); + if let Some(room) = self.room.upgrade() { + room.0 + .lock() + .paused_audio_tracks + .insert(self.server_track.sid.clone()); + } + } + + pub fn is_playing(&self) -> bool { + !self + .room + .upgrade() + .unwrap() + .0 + .lock() + .paused_audio_tracks + .contains(&self.server_track.sid) } } From 8f262892a04fedb1585f1d06785f9f699678eb41 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 14:53:24 -0800 Subject: [PATCH 208/334] Notify editors on buffer font size changes --- crates/editor/src/editor.rs | 7 ++++++- crates/theme/src/settings.rs | 11 ++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 288d25f9cd22e6245d3d743db8ddff1d1433a335..ce9c4215cc356c4f3128ff04cc6d44b8e306b576 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -97,7 +97,10 @@ use std::{ pub use sum_tree::Bias; use sum_tree::TreeMap; use text::{OffsetUtf16, Rope}; -use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; +use theme::{ + observe_buffer_font_size_adjustment, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, + ThemeColors, ThemeSettings, +}; use ui::{ h_flex, prelude::*, ButtonSize, ButtonStyle, IconButton, IconName, IconSize, ListItem, Popover, Tooltip, @@ -1812,6 +1815,7 @@ impl Editor { cx.observe(&display_map, Self::on_display_map_changed), cx.observe(&blink_manager, |_, _, cx| cx.notify()), cx.observe_global::(Self::settings_changed), + observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()), cx.observe_window_activation(|editor, cx| { let active = cx.is_window_active(); editor.blink_manager.update(cx, |blink_manager, cx| { @@ -8741,6 +8745,7 @@ impl Editor { )), cx, ); + cx.notify(); } pub fn set_searchable(&mut self, searchable: bool) { diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index e51ff81b012aceec95aa296e4b8d1a2b58642288..efc62ed59c429b97420fc276d1f2e2eca6c841cc 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -1,7 +1,9 @@ use crate::one_themes::one_dark; use crate::{Theme, ThemeRegistry}; use anyhow::Result; -use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; +use gpui::{ + px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels, Subscription, ViewContext, +}; use schemars::{ gen::SchemaGenerator, schema::{InstanceType, Schema, SchemaObject}, @@ -80,6 +82,13 @@ impl ThemeSettings { } } +pub fn observe_buffer_font_size_adjustment( + cx: &mut ViewContext, + f: impl 'static + Fn(&mut V, &mut ViewContext), +) -> Subscription { + cx.observe_global::(f) +} + pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels { if let Some(AdjustedBufferFontSize(adjusted_size)) = cx.try_global::() { let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; From 8f1633e7980495ffaa52bd606c118a64031071cb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 16:49:06 -0800 Subject: [PATCH 209/334] Iterate from leaf to root when marking views dirty in notify --- crates/gpui/src/window.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 2a88a4f3976646845589b5127e90c78bde6eb8c0..869d6b18268cc64bc18f9187dd34e8032237d28b 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2761,6 +2761,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { .rendered_frame .dispatch_tree .view_path(self.view.entity_id()) + .into_iter() + .rev() { if !self.window.dirty_views.insert(view_id) { break; From 1e755aa00f73b3d7897a1c4e6f4030ac31db0006 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 17:02:20 -0800 Subject: [PATCH 210/334] Notify global observers when removing a global --- crates/gpui/src/app.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 17f92efb581f3aed55becda1100316f041bdd308..41519f0ae4d3623a5b3e57c06a265c5f0a754b23 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -843,6 +843,7 @@ impl AppContext { /// Remove the global of the given type from the app context. Does not notify global observers. pub fn remove_global(&mut self) -> G { let global_type = TypeId::of::(); + self.push_effect(Effect::NotifyGlobalObservers { global_type }); *self .globals_by_type .remove(&global_type) From d84785f7e8519b30e474385e5a72f5726cf4783e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Jan 2024 17:03:02 -0800 Subject: [PATCH 211/334] Put back logic for resetting buffer font adjustment on settings change. --- crates/theme/src/theme.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index f8d90b7bdc823b0b52348fe94908454002616347..3b158ab9bb7f558f21ae72a64437b2b9f0909f9d 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -20,7 +20,7 @@ mod user_theme; use std::sync::Arc; -use ::settings::Settings; +use ::settings::{Settings, SettingsStore}; pub use default_colors::*; pub use default_theme::*; pub use registry::*; @@ -62,13 +62,22 @@ pub enum LoadThemes { pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { cx.set_global(ThemeRegistry::default()); + ThemeSettings::register(cx); + + let mut prev_buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; + cx.observe_global::(move |cx| { + let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; + if buffer_font_size != prev_buffer_font_size { + prev_buffer_font_size = buffer_font_size; + reset_font_size(cx); + } + }) + .detach(); match themes_to_load { LoadThemes::JustBase => (), LoadThemes::All => cx.global_mut::().load_user_themes(), } - - ThemeSettings::register(cx); } pub trait ActiveTheme { From 268d156fad72d410f479632f68a98bf8ec101748 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Mon, 15 Jan 2024 20:22:47 -0500 Subject: [PATCH 212/334] Add command palette action events --- Cargo.lock | 1 + crates/client/src/telemetry.rs | 15 +++++++++++ crates/command_palette/Cargo.toml | 3 ++- crates/command_palette/src/command_palette.rs | 27 ++++++++++++++++--- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 056fab49cd576915fca5443922b0f094591237e1..090980ac3eb0ebba8d4e12ad9b65c9aff5b1682e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1607,6 +1607,7 @@ name = "command_palette" version = "0.1.0" dependencies = [ "anyhow", + "client", "collections", "ctor", "editor", diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 6276548e4cf3a3cd380dbf9b3a19430fd10922f9..32cf9efba230da83f68d568818d20a52bfc862dd 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -129,6 +129,11 @@ pub enum Event { environment: &'static str, milliseconds_since_first_event: i64, }, + Action { + source: &'static str, + action: String, + milliseconds_since_first_event: i64, + }, } #[cfg(debug_assertions)] @@ -420,6 +425,16 @@ impl Telemetry { } } + pub fn report_action_event(self: &Arc, source: &'static str, action: String) { + let event = Event::Action { + source, + action, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_event(event) + } + fn milliseconds_since_first_event(&self) -> i64 { let mut state = self.state.lock(); diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 39ed4fd95e183db7306bbcbfca616af9bc7a6b96..c762af7c487e1603b4c3b38f7887f38a56e99276 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -9,6 +9,7 @@ path = "src/command_palette.rs" doctest = false [dependencies] +client = { path = "../client" } collections = { path = "../collections" } editor = { path = "../editor" } fuzzy = { path = "../fuzzy" } @@ -16,9 +17,9 @@ gpui = { path = "../gpui" } picker = { path = "../picker" } project = { path = "../project" } settings = { path = "../settings" } +theme = { path = "../theme" } ui = { path = "../ui" } util = { path = "../util" } -theme = { path = "../theme" } workspace = { path = "../workspace" } zed_actions = { path = "../zed_actions" } anyhow.workspace = true diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index e077426bb845dbc54efb9fc06906084946d41843..c90e44886568b6def4e7c66cced834553cf5bb96 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -3,6 +3,7 @@ use std::{ sync::Arc, }; +use client::telemetry::Telemetry; use collections::{CommandPaletteFilter, HashMap}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ @@ -39,11 +40,18 @@ impl CommandPalette { let Some(previous_focus_handle) = cx.focused() else { return; }; - workspace.toggle_modal(cx, move |cx| CommandPalette::new(previous_focus_handle, cx)); + let telemetry = workspace.client().telemetry().clone(); + workspace.toggle_modal(cx, move |cx| { + CommandPalette::new(previous_focus_handle, telemetry, cx) + }); }); } - fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext) -> Self { + fn new( + previous_focus_handle: FocusHandle, + telemetry: Arc, + cx: &mut ViewContext, + ) -> Self { let filter = cx.try_global::(); let commands = cx @@ -66,8 +74,12 @@ impl CommandPalette { }) .collect(); - let delegate = - CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle); + let delegate = CommandPaletteDelegate::new( + cx.view().downgrade(), + commands, + telemetry, + previous_focus_handle, + ); let picker = cx.new_view(|cx| Picker::new(delegate, cx)); Self { picker } @@ -103,6 +115,7 @@ pub struct CommandPaletteDelegate { commands: Vec, matches: Vec, selected_ix: usize, + telemetry: Arc, previous_focus_handle: FocusHandle, } @@ -130,6 +143,7 @@ impl CommandPaletteDelegate { fn new( command_palette: WeakView, commands: Vec, + telemetry: Arc, previous_focus_handle: FocusHandle, ) -> Self { Self { @@ -138,6 +152,7 @@ impl CommandPaletteDelegate { matches: vec![], commands, selected_ix: 0, + telemetry, previous_focus_handle, } } @@ -284,6 +299,10 @@ impl PickerDelegate for CommandPaletteDelegate { } let action_ix = self.matches[self.selected_ix].candidate_id; let command = self.commands.swap_remove(action_ix); + + self.telemetry + .report_action_event("command palette", command.name.clone()); + self.matches.clear(); self.commands.clear(); cx.update_global(|hit_counts: &mut HitCounts, _| { From 62232060e6f029f0204cc159f0b3822ef5071a7a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 15 Jan 2024 19:20:35 -0700 Subject: [PATCH 213/334] Close modals when focus leaves This is more similar to zed1's behaviour, and allows us to work around the difficulty in defining `on_mouse_down_out` for modals that have overlays. --- crates/workspace/src/modal_layer.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index d940f1d16842a712bb8aaef23281c5e8ab06217f..ded0ef1970edb58460f507afbf458d037b0f515f 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -27,7 +27,7 @@ impl ModalViewHandle for View { pub struct ActiveModal { modal: Box, - _subscription: Subscription, + _subscriptions: [Subscription; 2], previous_focus_handle: Option, focus_handle: FocusHandle, } @@ -61,13 +61,19 @@ impl ModalLayer { where V: ModalView, { + let focus_handle = cx.focus_handle(); self.active_modal = Some(ActiveModal { modal: Box::new(new_modal.clone()), - _subscription: cx.subscribe(&new_modal, |this, _, _: &DismissEvent, cx| { - this.hide_modal(cx); - }), + _subscriptions: [ + cx.subscribe(&new_modal, |this, _, _: &DismissEvent, cx| { + this.hide_modal(cx); + }), + cx.on_focus_out(&focus_handle, |this, cx| { + this.hide_modal(cx); + }), + ], previous_focus_handle: cx.focused(), - focus_handle: cx.focus_handle(), + focus_handle, }); cx.focus_view(&new_modal); cx.notify(); @@ -127,13 +133,7 @@ impl Render for ModalLayer { .flex_col() .items_center() .track_focus(&active_modal.focus_handle) - .child( - h_flex() - .on_mouse_down_out(cx.listener(|this, _, cx| { - this.hide_modal(cx); - })) - .child(active_modal.modal.view()), - ), + .child(h_flex().child(active_modal.modal.view())), ) } } From 036e637208525b477d6ed5c3bddab4fd49e737a1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 15 Jan 2024 19:31:01 -0700 Subject: [PATCH 214/334] Disallow self-management for admins --- crates/collab_ui/src/collab_panel/channel_modal.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index c207e31bbeaa2b087578860c09ae176827074388..11890bcbe6d3dace458827583c13d331c6670e7b 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -348,6 +348,10 @@ impl PickerDelegate for ChannelModalDelegate { fn confirm(&mut self, _: bool, cx: &mut ViewContext>) { if let Some((selected_user, role)) = self.user_at_index(self.selected_index) { + if Some(selected_user.id) == self.user_store.read(cx).current_user().map(|user| user.id) + { + return; + } match self.mode { Mode::ManageMembers => { self.show_context_menu(selected_user, role.unwrap_or(ChannelRole::Member), cx) @@ -383,6 +387,7 @@ impl PickerDelegate for ChannelModalDelegate { ) -> Option { let (user, role) = self.user_at_index(ix)?; let request_status = self.member_status(user.id, cx); + let is_me = self.user_store.read(cx).current_user().map(|user| user.id) == Some(user.id); Some( ListItem::new(ix) @@ -406,7 +411,10 @@ impl PickerDelegate for ChannelModalDelegate { Some(ChannelRole::Guest) => Some(Label::new("Guest")), _ => None, }) - .child(IconButton::new("ellipsis", IconName::Ellipsis)) + .when(!is_me, |el| { + el.child(IconButton::new("ellipsis", IconName::Ellipsis)) + }) + .when(is_me, |el| el.child(Label::new("You").color(Color::Muted))) .children( if let (Some((menu, _)), true) = (&self.context_menu, selected) { Some( From 18739477f7b490742714c675cf6afe170abf0f7a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 15 Jan 2024 20:12:20 -0700 Subject: [PATCH 215/334] Clippy --- crates/workspace/src/modal_layer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace/src/modal_layer.rs b/crates/workspace/src/modal_layer.rs index ded0ef1970edb58460f507afbf458d037b0f515f..c30ca35a68578cc3c8a77f4bbf3fa702291181fa 100644 --- a/crates/workspace/src/modal_layer.rs +++ b/crates/workspace/src/modal_layer.rs @@ -114,7 +114,7 @@ impl ModalLayer { } impl Render for ModalLayer { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + fn render(&mut self, _: &mut ViewContext) -> impl IntoElement { let Some(active_modal) = &self.active_modal else { return div(); }; From bdb06f183b09a15340c9a83221432d54fc10a41a Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 16 Jan 2024 00:07:06 -0500 Subject: [PATCH 216/334] Add a rudimentary state color builder --- Cargo.lock | 1 + crates/color/src/color.rs | 57 +++++++++++++++++++++++++++++++++------ crates/theme/Cargo.toml | 1 + crates/theme/src/theme.rs | 7 +++++ 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b0c6e4bb97ebc9fb8ce9615729fb370a5b18a34..728fc695ac0891bb20f06a70d6c35ee2dfe65876 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7925,6 +7925,7 @@ name = "theme" version = "0.1.0" dependencies = [ "anyhow", + "color", "fs", "gpui", "indexmap 1.9.3", diff --git a/crates/color/src/color.rs b/crates/color/src/color.rs index 77818eb7b8f76c23cc9b05e31402ad04a89aeca3..8a7f3f17525cd097dfb4072c43b83675f54323c3 100644 --- a/crates/color/src/color.rs +++ b/crates/color/src/color.rs @@ -88,25 +88,28 @@ pub struct Color { } impl Color { - /// Creates a new [`Color`] - pub fn new(hue: f32, saturation: f32, lightness: f32) -> Self { - let hsl = hsl(hue, saturation, lightness); - - Self { value: hsl.into() } - } - /// Creates a new [`Color`] with an alpha value. - pub fn from_hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self { + pub fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self { Self { value: Hsla::new(hue, saturation, lightness, alpha), } } + /// Creates a new [`Color`] with an alpha value of `1.0`. + pub fn hsl(hue: f32, saturation: f32, lightness: f32) -> Self { + Self::new(hue, saturation, lightness, 1.0) + } + /// Returns the [`palette::Hsla`] value of this color. pub fn value(&self) -> Hsla { self.value } + /// Returns a set of states for this color. + pub fn states(&self, is_light: bool) -> ColorStates { + states_for_color(*self, is_light) + } + /// Mixes this color with another [`palette::Hsl`] color at the given `mix_ratio`. pub fn mix(&self, other: Hsl, mix_ratio: f32) -> Self { let mixed = self.value.mix(other.into(), mix_ratio); @@ -116,3 +119,41 @@ impl Color { } } } + +/// A set of colors for different states of an element. +#[derive(Debug, Copy, Clone)] +pub struct ColorStates { + /// The default color. + pub default: Color, + /// The color when the mouse is hovering over the element. + pub hover: Color, + /// The color when the mouse button is held down on the element. + pub active: Color, + /// The color when the element is focused with the keyboard. + pub focused: Color, + /// The color when the element is disabled. + pub disabled: Color, +} + +/// Returns a set of colors for different states of an element. +/// +/// todo!("Test and improve this function") +pub fn states_for_color(color: Color, is_light: bool) -> ColorStates { + let hover_lightness = if is_light { 0.9 } else { 0.1 }; + let active_lightness = if is_light { 0.8 } else { 0.2 }; + let focused_lightness = if is_light { 0.7 } else { 0.3 }; + let disabled_lightness = if is_light { 0.6 } else { 0.5 }; + + let hover = color.mix(hsl(0.0, 0.0, hover_lightness), 0.1); + let active = color.mix(hsl(0.0, 0.0, active_lightness), 0.1); + let focused = color.mix(hsl(0.0, 0.0, focused_lightness), 0.1); + let disabled = color.mix(hsl(0.0, 0.0, disabled_lightness), 0.1); + + ColorStates { + default: color, + hover, + active, + focused, + disabled, + } +} diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 1c30176b25b41130b79ccbc8879167fe34275895..428bcaac10b76501c78a772652f44370d4a74156 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -34,6 +34,7 @@ story = { path = "../story", optional = true } toml.workspace = true uuid.workspace = true util = { path = "../util" } +color = {path = "../color"} itertools = { version = "0.11.0", optional = true } [dev-dependencies] diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index f8d90b7bdc823b0b52348fe94908454002616347..c40d7c8ceb2a1d99b1e7b97f7efb0b18b9bea48f 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -147,3 +147,10 @@ pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla { color.a = alpha; color } + +pub fn to_gpui_hsla(color: color::Color) -> gpui::Hsla { + let hsla = color.value(); + let hue: f32 = hsla.hue.into(); + + gpui::hsla(hue / 360.0, hsla.saturation, hsla.lightness, hsla.alpha) +} From dde0056845d31e1bcc60feb28e63622502017e0a Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 16 Jan 2024 01:08:17 -0500 Subject: [PATCH 217/334] Use srgb, get mix and blend working --- crates/color/src/color.rs | 160 +++++++++++++++++++++++++------------- crates/theme/src/theme.rs | 7 -- 2 files changed, 107 insertions(+), 60 deletions(-) diff --git a/crates/color/src/color.rs b/crates/color/src/color.rs index 8a7f3f17525cd097dfb4072c43b83675f54323c3..d3d832099af16d85f2afd0e5e6551bca966618c6 100644 --- a/crates/color/src/color.rs +++ b/crates/color/src/color.rs @@ -7,7 +7,9 @@ //! **Note:** This crate does not depend on `gpui`, so it does not provide any //! interfaces for converting to `gpui` style colors. -use palette::{FromColor, Hsl, Hsla, Mix, Srgba, WithAlpha}; +use palette::{ + blend::Blend, convert::FromColorUnclamped, encoding, rgb::Rgb, Clamp, Mix, Srgb, WithAlpha, +}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum BlendMode { @@ -24,16 +26,11 @@ pub enum BlendMode { Exclusion, } -/// Creates a new [`palette::Hsl`] color. -pub fn hsl(h: f32, s: f32, l: f32) -> Hsl { - Hsl::new_srgb(h, s, l) -} - /// Converts a hexadecimal color string to a `palette::Hsla` color. /// /// This function supports the following hex formats: /// `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`. -pub fn hex_to_hsla(s: &str) -> Result { +pub fn hex_to_hsla(s: &str) -> Result { let hex = s.trim_start_matches('#'); // Expand shorthand formats #RGB and #RGBA to #RRGGBB and #RRGGBBAA @@ -64,64 +61,112 @@ pub fn hex_to_hsla(s: &str) -> Result { let b = ((hex_val >> 8) & 0xFF) as f32 / 255.0; let a = (hex_val & 0xFF) as f32 / 255.0; - let srgba = Srgba::new(r, g, b, a); - let hsl = Hsl::from_color(srgba); - let hsla = Hsla::from(hsl).with_alpha(a); + let color = Color { r, g, b, a }; - Ok(hsla) + Ok(color) } -/// Mixes two [`palette::Hsl`] colors at the given `mix_ratio`. -pub fn hsl_mix(hsla_1: Hsl, hsla_2: Hsl, mix_ratio: f32) -> Hsl { - hsla_1.mix(hsla_2, mix_ratio).into() +// This implements conversion to and from all Palette colors. +#[derive(FromColorUnclamped, WithAlpha, Debug, Clone)] +// We have to tell Palette that we will take care of converting to/from sRGB. +#[palette(skip_derives(Rgb), rgb_standard = "encoding::Srgb")] +pub struct Color { + r: f32, + g: f32, + b: f32, + // Let Palette know this is our alpha channel. + #[palette(alpha)] + a: f32, } -/// Represents a color -/// An interstitial state used to provide a consistent API for colors -/// with additional functionality like color mixing, blending, etc. -/// -/// Does not return [gpui] colors as the `color` crate does not -/// depend on [gpui]. -#[derive(Debug, Copy, Clone)] -pub struct Color { - value: Hsla, +// There's no blanket implementation for Self -> Self, unlike the From trait. +// This is to better allow cases like Self -> Self. +impl FromColorUnclamped for Color { + fn from_color_unclamped(color: Color) -> Color { + color + } } -impl Color { - /// Creates a new [`Color`] with an alpha value. - pub fn new(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Self { - Self { - value: Hsla::new(hue, saturation, lightness, alpha), +// Convert from any kind of f32 sRGB. +impl FromColorUnclamped> for Color +where + Srgb: FromColorUnclamped>, +{ + fn from_color_unclamped(color: Rgb) -> Color { + let srgb = Srgb::from_color_unclamped(color); + Color { + r: srgb.red, + g: srgb.green, + b: srgb.blue, + a: 1.0, } } +} + +// Convert into any kind of f32 sRGB. +impl FromColorUnclamped for Rgb +where + Rgb: FromColorUnclamped, +{ + fn from_color_unclamped(color: Color) -> Self { + let srgb = Srgb::new(color.r, color.g, color.b); + Self::from_color_unclamped(srgb) + } +} - /// Creates a new [`Color`] with an alpha value of `1.0`. - pub fn hsl(hue: f32, saturation: f32, lightness: f32) -> Self { - Self::new(hue, saturation, lightness, 1.0) +// Add the required clamping. +impl Clamp for Color { + fn clamp(self) -> Self { + Color { + r: self.r.min(1.0).max(0.0), + g: self.g.min(1.0).max(0.0), + b: self.b.min(1.0).max(0.0), + a: self.a.min(1.0).max(0.0), + } } +} - /// Returns the [`palette::Hsla`] value of this color. - pub fn value(&self) -> Hsla { - self.value +impl Color { + pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + Color { r, g, b, a } } /// Returns a set of states for this color. - pub fn states(&self, is_light: bool) -> ColorStates { - states_for_color(*self, is_light) + pub fn states(self, is_light: bool) -> ColorStates { + states_for_color(self, is_light) } /// Mixes this color with another [`palette::Hsl`] color at the given `mix_ratio`. - pub fn mix(&self, other: Hsl, mix_ratio: f32) -> Self { - let mixed = self.value.mix(other.into(), mix_ratio); + pub fn mixed(&self, other: Color, mix_ratio: f32) -> Self { + let srgb_self = Srgb::new(self.r, self.g, self.b); + let srgb_other = Srgb::new(other.r, other.g, other.b); + + // Directly mix the colors as sRGB values + let mixed = srgb_self.mix(srgb_other, mix_ratio); + Color::from_color_unclamped(mixed) + } + + pub fn blend(&self, other: Color, blend_mode: BlendMode) -> Self { + let srgb_self = Srgb::new(self.r, self.g, self.b); + let srgb_other = Srgb::new(other.r, other.g, other.b); + + let blended = match blend_mode { + // replace hsl methods with the respective sRGB methods + BlendMode::Multiply => srgb_self.multiply(srgb_other), + _ => unimplemented!(), + }; Self { - value: mixed.into(), + r: blended.red, + g: blended.green, + b: blended.blue, + a: self.a, } } } /// A set of colors for different states of an element. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub struct ColorStates { /// The default color. pub default: Color, @@ -139,21 +184,30 @@ pub struct ColorStates { /// /// todo!("Test and improve this function") pub fn states_for_color(color: Color, is_light: bool) -> ColorStates { - let hover_lightness = if is_light { 0.9 } else { 0.1 }; - let active_lightness = if is_light { 0.8 } else { 0.2 }; - let focused_lightness = if is_light { 0.7 } else { 0.3 }; - let disabled_lightness = if is_light { 0.6 } else { 0.5 }; + let adjustment_factor = if is_light { 0.1 } else { -0.1 }; + let hover_adjustment = 1.0 - adjustment_factor; + let active_adjustment = 1.0 - 2.0 * adjustment_factor; + let focused_adjustment = 1.0 - 3.0 * adjustment_factor; + let disabled_adjustment = 1.0 - 4.0 * adjustment_factor; + + let make_adjustment = |color: Color, adjustment: f32| -> Color { + // Adjust lightness for each state + // Note: Adjustment logic may differ; simplify as needed for sRGB + Color::new( + color.r * adjustment, + color.g * adjustment, + color.b * adjustment, + color.a, + ) + }; - let hover = color.mix(hsl(0.0, 0.0, hover_lightness), 0.1); - let active = color.mix(hsl(0.0, 0.0, active_lightness), 0.1); - let focused = color.mix(hsl(0.0, 0.0, focused_lightness), 0.1); - let disabled = color.mix(hsl(0.0, 0.0, disabled_lightness), 0.1); + let color = color.clamp(); ColorStates { - default: color, - hover, - active, - focused, - disabled, + default: color.clone(), + hover: make_adjustment(color.clone(), hover_adjustment), + active: make_adjustment(color.clone(), active_adjustment), + focused: make_adjustment(color.clone(), focused_adjustment), + disabled: make_adjustment(color.clone(), disabled_adjustment), } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c40d7c8ceb2a1d99b1e7b97f7efb0b18b9bea48f..f8d90b7bdc823b0b52348fe94908454002616347 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -147,10 +147,3 @@ pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla { color.a = alpha; color } - -pub fn to_gpui_hsla(color: color::Color) -> gpui::Hsla { - let hsla = color.value(); - let hue: f32 = hsla.hue.into(); - - gpui::hsla(hue / 360.0, hsla.saturation, hsla.lightness, hsla.alpha) -} From fbb363e83a066a516c7d03a0c75c7b5840673e4f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 Jan 2024 10:07:31 +0200 Subject: [PATCH 218/334] Add tests --- crates/editor/src/editor_tests.rs | 157 ++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 520c3714d3d529dbcd2df4d4cc4d750db2a7a53c..8add11b963b269b11aedcfb8af4d6e063d18e240 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3821,62 +3821,133 @@ async fn test_select_next(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_select_previous(cx: &mut gpui::TestAppContext) { +async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - { - // `Select previous` without a selection (selects wordwise) - let mut cx = EditorTestContext::new(cx).await; - cx.set_state("abc\nˇabc abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); + let mut cx = EditorTestContext::new(cx).await; + cx.set_state( + r#"let foo = 2; +lˇet foo = 2; +let fooˇ = 2; +let foo = 2; +let foo = ˇ2;"#, + ); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); + cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)) + .unwrap(); + cx.assert_editor_state( + r#"let foo = 2; +«letˇ» foo = 2; +let «fooˇ» = 2; +let foo = 2; +let foo = «2ˇ»;"#, + ); - cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); - cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); + // noop for multiple selections with different contents + cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx)) + .unwrap(); + cx.assert_editor_state( + r#"let foo = 2; +«letˇ» foo = 2; +let «fooˇ» = 2; +let foo = 2; +let foo = «2ˇ»;"#, + ); +} - cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); - cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); +#[gpui::test] +async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»"); + let mut cx = EditorTestContext::new(cx).await; + cx.set_state("abc\nˇabc abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); - } - { - // `Select previous` with a selection - let mut cx = EditorTestContext::new(cx).await; - cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc"); + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); + cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc"); - cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); + cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc"); - cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»"); - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»"); + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); +} - cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) - .unwrap(); - cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»"); - } +#[gpui::test] +async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + cx.set_state( + r#"let foo = 2; +lˇet foo = 2; +let fooˇ = 2; +let foo = 2; +let foo = ˇ2;"#, + ); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state( + r#"let foo = 2; +«letˇ» foo = 2; +let «fooˇ» = 2; +let foo = 2; +let foo = «2ˇ»;"#, + ); + + // noop for multiple selections with different contents + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state( + r#"let foo = 2; +«letˇ» foo = 2; +let «fooˇ» = 2; +let foo = 2; +let foo = «2ˇ»;"#, + ); +} + +#[gpui::test] +async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + + cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx)); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc"); + + cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx)); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»"); } #[gpui::test] From 25abe8f981f20b480bc43e8c705db9640ed83662 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 Jan 2024 10:56:57 +0200 Subject: [PATCH 219/334] Fix the tests --- crates/editor/src/editor.rs | 55 +++++++++++++++---------------- crates/editor/src/editor_tests.rs | 6 +++- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 55c31cfa8074734343dd62bb6e138129d77b0d9b..4856170b321fcf6dcdb5907a40c5e578a5d6e5c6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6469,8 +6469,8 @@ impl Editor { self.select_next_state = Some(select_next_state); } else { let mut only_carets = true; - let mut same_letters_selected = true; - let mut selection_query = None; + let mut same_text_selected = true; + let mut selected_text = None; let mut selections_iter = selections.iter().peekable(); while let Some(selection) = selections_iter.next() { @@ -6478,24 +6478,24 @@ impl Editor { only_carets = false; } - if same_letters_selected { - if selection_query.is_none() { - selection_query = + if same_text_selected { + if selected_text.is_none() { + selected_text = Some(buffer.text_for_range(selection.range()).collect::()); } if let Some(next_selection) = selections_iter.peek() { if next_selection.range().len() == selection.range().len() { - let next_query = buffer + let next_selected_text = buffer .text_for_range(next_selection.range()) .collect::(); - if Some(next_query) != selection_query { - same_letters_selected = false; - selection_query = None; + if Some(next_selected_text) != selected_text { + same_text_selected = false; + selected_text = None; } } else { - same_letters_selected = false; - selection_query = None; + same_text_selected = false; + selected_text = None; } } } @@ -6537,9 +6537,9 @@ impl Editor { } else { self.select_next_state = None; } - } else if let Some(query) = selection_query { + } else if let Some(selected_text) = selected_text { self.select_next_state = Some(SelectNextState { - query: AhoCorasick::new(&[query])?, + query: AhoCorasick::new(&[selected_text])?, wordwise: false, done: false, }); @@ -6585,7 +6585,6 @@ impl Editor { Ok(()) } - // TODO kb test both select_next and select_previous pub fn select_previous( &mut self, action: &SelectPrevious, @@ -6646,8 +6645,8 @@ impl Editor { self.select_prev_state = Some(select_prev_state); } else { let mut only_carets = true; - let mut same_letters_selected = true; - let mut selection_query = None; + let mut same_text_selected = true; + let mut selected_text = None; let mut selections_iter = selections.iter().peekable(); while let Some(selection) = selections_iter.next() { @@ -6655,24 +6654,24 @@ impl Editor { only_carets = false; } - if same_letters_selected { - if selection_query.is_none() { - selection_query = + if same_text_selected { + if selected_text.is_none() { + selected_text = Some(buffer.text_for_range(selection.range()).collect::()); } if let Some(next_selection) = selections_iter.peek() { if next_selection.range().len() == selection.range().len() { - let next_query = buffer + let next_selected_text = buffer .text_for_range(next_selection.range()) .collect::(); - if Some(next_query) != selection_query { - same_letters_selected = false; - selection_query = None; + if Some(next_selected_text) != selected_text { + same_text_selected = false; + selected_text = None; } } else { - same_letters_selected = false; - selection_query = None; + same_text_selected = false; + selected_text = None; } } } @@ -6698,7 +6697,7 @@ impl Editor { .collect::(); let is_empty = query.is_empty(); let select_state = SelectNextState { - query: AhoCorasick::new(&[query])?, + query: AhoCorasick::new(&[query.chars().rev().collect::()])?, wordwise: true, done: is_empty, }; @@ -6716,9 +6715,9 @@ impl Editor { self.change_selections(Some(Autoscroll::newest()), cx, |s| { s.select(selections); }); - } else if let Some(query) = selection_query { + } else if let Some(selected_text) = selected_text { self.select_prev_state = Some(SelectNextState { - query: AhoCorasick::new(&[query])?, + query: AhoCorasick::new(&[selected_text.chars().rev().collect::()])?, wordwise: false, done: false, }); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 8add11b963b269b11aedcfb8af4d6e063d18e240..a6e3d19995c2126e57b698178e8d0cac1bc264c5 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3882,7 +3882,11 @@ async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) { cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) .unwrap(); - cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); + cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»"); + + cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx)) + .unwrap(); + cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»"); } #[gpui::test] From 074966427d1983d655626e5e8df5d719b652f5ae Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 16 Jan 2024 11:23:34 +0200 Subject: [PATCH 220/334] Fix previous theme not being applied on startup --- crates/theme/src/theme.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3b158ab9bb7f558f21ae72a64437b2b9f0909f9d..92a5877f514ead1143b89b4d6d217accc153d767 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -62,6 +62,10 @@ pub enum LoadThemes { pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { cx.set_global(ThemeRegistry::default()); + match themes_to_load { + LoadThemes::JustBase => (), + LoadThemes::All => cx.global_mut::().load_user_themes(), + } ThemeSettings::register(cx); let mut prev_buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size; @@ -73,11 +77,6 @@ pub fn init(themes_to_load: LoadThemes, cx: &mut AppContext) { } }) .detach(); - - match themes_to_load { - LoadThemes::JustBase => (), - LoadThemes::All => cx.global_mut::().load_user_themes(), - } } pub trait ActiveTheme { From 03bfe3ef80eb2f2af6932f6f0f9117a430328ecd Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 16 Jan 2024 12:01:54 +0100 Subject: [PATCH 221/334] Call CGGetActiveDisplayList once to avoid panic Previously we called CGGetActiveDisplayList twice: once to get the number of displays and then to get the displays. We saw a panic due to no displays being returned here. As a first attempt to fix the panic, we're reducing the amount of calls to CGGetActiveDisplayList and just do one with the trade-off being that we pre-allocate 32 pointers in a Vec. --- crates/gpui/src/platform/mac/display.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/mac/display.rs b/crates/gpui/src/platform/mac/display.rs index 25e0921fee28efd25c0c1ddf88d6df84318a19aa..ba15fbd90d8c05284049023f515f2539a5f8d8db 100644 --- a/crates/gpui/src/platform/mac/display.rs +++ b/crates/gpui/src/platform/mac/display.rs @@ -33,17 +33,20 @@ impl MacDisplay { /// Obtains an iterator over all currently active system displays. pub fn all() -> impl Iterator { unsafe { - let mut display_count: u32 = 0; - let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count); + // We're assuming there aren't more than 32 displays connected to the system. + let mut displays = Vec::with_capacity(32); + let mut display_count = 0; + let result = CGGetActiveDisplayList( + displays.capacity() as u32, + displays.as_mut_ptr(), + &mut display_count, + ); if result == 0 { - let mut displays = Vec::with_capacity(display_count as usize); - CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count); displays.set_len(display_count as usize); - displays.into_iter().map(MacDisplay) } else { - panic!("Failed to get active display list"); + panic!("Failed to get active display list. Result: {result}"); } } } From f938bae0a2122b5244a19bfef5d3b3b4c1a5335b Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 16 Jan 2024 14:23:10 +0100 Subject: [PATCH 222/334] Use NSScreen to fetch primary display According to Chromium source, `NSScreen::screens` should always get us one display. We made this change because we ran into panics caused by the previous `unwrap()` when `CGGetActiveDisplayList` might return an empty list. --- crates/gpui/src/platform/mac/display.rs | 23 ++++++++++++++++++++++- crates/gpui/src/platform/mac/window.rs | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/mac/display.rs b/crates/gpui/src/platform/mac/display.rs index ba15fbd90d8c05284049023f515f2539a5f8d8db..2b72c335c80e80f700d6caa25fd64d5c8bf4f414 100644 --- a/crates/gpui/src/platform/mac/display.rs +++ b/crates/gpui/src/platform/mac/display.rs @@ -1,10 +1,16 @@ use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay}; use anyhow::Result; +use cocoa::{ + appkit::NSScreen, + base::{id, nil}, + foundation::{NSDictionary, NSString}, +}; use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}; use core_graphics::{ display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList}, geometry::{CGPoint, CGRect, CGSize}, }; +use objc::{msg_send, sel, sel_impl}; use std::any::Any; use uuid::Uuid; @@ -27,7 +33,22 @@ impl MacDisplay { /// Get the primary screen - the one with the menu bar, and whose bottom left /// corner is at the origin of the AppKit coordinate system. pub fn primary() -> Self { - Self::all().next().unwrap() + // Instead of iterating through all active systems displays via `all()` we use the first + // NSScreen and gets its CGDirectDisplayID, because we can't be sure that `CGGetActiveDisplayList` + // will always return a list of active displays (machine might be sleeping). + // + // The following is what Chromium does too: + // + // https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/ui/display/mac/screen_mac.mm#56 + unsafe { + let screens = NSScreen::screens(nil); + let screen = cocoa::foundation::NSArray::objectAtIndex(screens, 0); + let device_description = NSScreen::deviceDescription(screen); + let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber"); + let screen_number = device_description.objectForKey_(screen_number_key); + let screen_number: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue]; + Self(screen_number) + } } /// Obtains an iterator over all currently active system displays. diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 4c71141cdce41b1492b48bd00048255d5763e535..c364021281a3690635713bd756c520b1d5f3558b 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -484,7 +484,7 @@ impl MacWindow { let display = options .display_id - .and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id)) + .and_then(MacDisplay::find_by_id) .unwrap_or_else(MacDisplay::primary); let mut target_screen = nil; From 4e0c8dcea99ad6a71210760539ddfcc6fef3a94a Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 16 Jan 2024 10:44:24 -0500 Subject: [PATCH 223/334] Revert "Use taffy to retrieve the parent for a given layout node" This reverts commit 5904bcf1c20638d63b244a1b2b038ec9a664ba1c. Co-Authored-By: Antonio Scandurra --- Cargo.lock | 7 +++--- crates/gpui/Cargo.toml | 2 +- crates/gpui/src/taffy.rs | 46 ++++++++++++++++++++++++---------------- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 090980ac3eb0ebba8d4e12ad9b65c9aff5b1682e..7b1fe5144c5b95c0adc0c02a2c28dd3cec44fa09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3116,9 +3116,9 @@ dependencies = [ [[package]] name = "grid" -version = "0.13.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d196ffc1627db18a531359249b2bf8416178d84b729f3cebeb278f285fb9b58c" +checksum = "1df00eed8d1f0db937f6be10e46e8072b0671accb504cf0f959c5c52c679f5b9" [[package]] name = "h2" @@ -7703,12 +7703,11 @@ dependencies = [ [[package]] name = "taffy" version = "0.3.11" -source = "git+https://github.com/zed-industries/taffy?rev=5e6c2d23e70e9f2156911d11050cb686362ba277#5e6c2d23e70e9f2156911d11050cb686362ba277" +source = "git+https://github.com/DioxusLabs/taffy?rev=1876f72bee5e376023eaa518aa7b8a34c769bd1b#1876f72bee5e376023eaa518aa7b8a34c769bd1b" dependencies = [ "arrayvec 0.7.4", "grid", "num-traits", - "serde", "slotmap", ] diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 333f5a1649f3c73ec8a31664dfc7a74d88158cb3..ee7549287378f537de71b08abf59bc6af2ab4a98 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -46,7 +46,7 @@ serde_derive.workspace = true serde_json.workspace = true smallvec.workspace = true smol.workspace = true -taffy = { git = "https://github.com/zed-industries/taffy", rev = "5e6c2d23e70e9f2156911d11050cb686362ba277" } +taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "1876f72bee5e376023eaa518aa7b8a34c769bd1b" } thiserror.workspace = true time.workspace = true tiny-skia = "0.5" diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 40098b090bc6d2156fbaaf0d1fee7ff6e210b8e7..26d5a2e69ea2dcc8e1664b4fa8ef885f84e8feeb 100644 --- a/crates/gpui/src/taffy.rs +++ b/crates/gpui/src/taffy.rs @@ -6,13 +6,16 @@ use collections::{FxHashMap, FxHashSet}; use smallvec::SmallVec; use std::fmt::Debug; use taffy::{ - AvailableSpace as TaffyAvailableSpace, NodeId, Point as TaffyPoint, Rect as TaffyRect, - Size as TaffySize, TaffyTree, TraversePartialTree, + geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize}, + style::AvailableSpace as TaffyAvailableSpace, + tree::NodeId, + Taffy, }; pub struct TaffyLayoutEngine { - tree: TaffyTree, + taffy: Taffy, styles: FxHashMap, + children_to_parents: FxHashMap, absolute_layout_bounds: FxHashMap>, computed_layouts: FxHashSet, nodes_to_measure: FxHashMap< @@ -32,8 +35,9 @@ static EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by constructi impl TaffyLayoutEngine { pub fn new() -> Self { TaffyLayoutEngine { - tree: TaffyTree::new(), + taffy: Taffy::new(), styles: FxHashMap::default(), + children_to_parents: FxHashMap::default(), absolute_layout_bounds: FxHashMap::default(), computed_layouts: FxHashSet::default(), nodes_to_measure: FxHashMap::default(), @@ -41,7 +45,8 @@ impl TaffyLayoutEngine { } pub fn clear(&mut self) { - self.tree.clear(); + self.taffy.clear(); + self.children_to_parents.clear(); self.absolute_layout_bounds.clear(); self.computed_layouts.clear(); self.nodes_to_measure.clear(); @@ -60,16 +65,21 @@ impl TaffyLayoutEngine { ) -> LayoutId { let taffy_style = style.to_taffy(rem_size); let layout_id = if children.is_empty() { - self.tree + self.taffy .new_leaf(taffy_style) .expect(EXPECT_MESSAGE) .into() } else { - self.tree + let parent_id = self + .taffy // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId. .new_with_children(taffy_style, unsafe { std::mem::transmute(children) }) .expect(EXPECT_MESSAGE) - .into() + .into(); + for child_id in children { + self.children_to_parents.insert(*child_id, parent_id); + } + parent_id }; self.styles.insert(layout_id, style.clone()); layout_id @@ -86,7 +96,7 @@ impl TaffyLayoutEngine { let taffy_style = style.to_taffy(rem_size); let layout_id = self - .tree + .taffy .new_leaf_with_context(taffy_style, ()) .expect(EXPECT_MESSAGE) .into(); @@ -100,7 +110,7 @@ impl TaffyLayoutEngine { fn count_all_children(&self, parent: LayoutId) -> anyhow::Result { let mut count = 0; - for child in self.tree.children(parent.0)? { + for child in self.taffy.children(parent.0)? { // Count this child. count += 1; @@ -116,12 +126,12 @@ impl TaffyLayoutEngine { fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result { println!( "{parent:?} at depth {depth} has {} children", - self.tree.child_count(parent.0) + self.taffy.child_count(parent.0)? ); let mut max_child_depth = 0; - for child in self.tree.children(parent.0)? { + for child in self.taffy.children(parent.0)? { max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?); } @@ -133,7 +143,7 @@ impl TaffyLayoutEngine { fn get_edges(&self, parent: LayoutId) -> anyhow::Result> { let mut edges = Vec::new(); - for child in self.tree.children(parent.0)? { + for child in self.taffy.children(parent.0)? { edges.push((parent, LayoutId(child))); edges.extend(self.get_edges(LayoutId(child))?); @@ -166,7 +176,7 @@ impl TaffyLayoutEngine { while let Some(id) = stack.pop() { self.absolute_layout_bounds.remove(&id); stack.extend( - self.tree + self.taffy .children(id.into()) .expect(EXPECT_MESSAGE) .into_iter() @@ -176,7 +186,7 @@ impl TaffyLayoutEngine { } // let started_at = std::time::Instant::now(); - self.tree + self.taffy .compute_layout_with_measure( id.into(), available_space.into(), @@ -203,14 +213,14 @@ impl TaffyLayoutEngine { return layout; } - let layout = self.tree.layout(id.into()).expect(EXPECT_MESSAGE); + let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE); let mut bounds = Bounds { origin: layout.location.into(), size: layout.size.into(), }; - if let Some(parent_id) = self.tree.parent(id.0) { - let parent_bounds = self.layout_bounds(parent_id.into()); + if let Some(parent_id) = self.children_to_parents.get(&id).copied() { + let parent_bounds = self.layout_bounds(parent_id); bounds.origin += parent_bounds.origin; } self.absolute_layout_bounds.insert(id, bounds); From 1cbdf2ba22b064e53372c5eda56a75caaf49718e Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 16 Jan 2024 16:46:24 +0100 Subject: [PATCH 224/334] Fix overlapping block headers when using custom line height This fixes block headers overlapping over text in the buffer when using a custom line height of 1.25. It fixes the issue by making the parent container a v-flex, vertically-justifying the content and moving from relative padding to absolute padding for the header itself. Co-authored-by: antonio Co-authored-by: julia Co-authored-by: marshall --- crates/editor/src/element.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 895df153406934f9d38dca0d25994baf6f69adb6..b82bd55bcf5e898299ca8a3a0e1d88cf37c72367 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2288,17 +2288,18 @@ impl EditorElement { .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/")); } - div() + v_flex() .id(("path header container", block_id)) .size_full() - .p_1p5() + .justify_center() + .p(gpui::px(6.)) .child( h_flex() .id("path header block") - .py_1p5() - .pl_3() - .pr_2() - .rounded_lg() + .size_full() + .pl(gpui::px(12.)) + .pr(gpui::px(8.)) + .rounded_md() .shadow_md() .border() .border_color(cx.theme().colors().border) @@ -2861,6 +2862,7 @@ impl Element for EditorElement { cx.with_text_style( Some(gpui::TextStyleRefinement { font_size: Some(self.style.text.font_size), + line_height: Some(self.style.text.line_height), ..Default::default() }), |cx| { From 62f5becf1fff6a3921bef734c92312a6c205bd54 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 16 Jan 2024 16:51:08 +0100 Subject: [PATCH 225/334] Fix rustfmt by pulling out long string into constant --- crates/search/src/project_search.rs | 67 +++++++++++++++-------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 3827b46c67fc29c96c5b361440fb66143c96db1a..0572b6eab051c3c8baaacdd366ab264126f53d0c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -282,6 +282,8 @@ impl EventEmitter for ProjectSearchView {} impl Render for ProjectSearchView { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + const PLEASE_AUTHENTICATE: &str = "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables. If you authenticated using the Assistant Panel, please restart Zed to Authenticate."; + if self.has_matches() { div() .flex_1() @@ -303,40 +305,39 @@ impl Render for ProjectSearchView { let mut show_minor_text = true; let semantic_status = self.semantic_state.as_ref().and_then(|semantic| { let status = semantic.index_status; - match status { - SemanticIndexStatus::NotAuthenticated => { - major_text = Label::new("Not Authenticated"); - show_minor_text = false; - Some( - "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables. If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string()) - } - SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), - SemanticIndexStatus::Indexing { - remaining_files, - rate_limit_expiry, - } => { - if remaining_files == 0 { - Some("Indexing...".to_string()) - } else { - if let Some(rate_limit_expiry) = rate_limit_expiry { - let remaining_seconds = - rate_limit_expiry.duration_since(Instant::now()); - if remaining_seconds > Duration::from_secs(0) { - Some(format!( - "Remaining files to index (rate limit resets in {}s): {}", - remaining_seconds.as_secs(), - remaining_files - )) - } else { - Some(format!("Remaining files to index: {}", remaining_files)) - } - } else { - Some(format!("Remaining files to index: {}", remaining_files)) - } - } - } - SemanticIndexStatus::NotIndexed => None, + match status { + SemanticIndexStatus::NotAuthenticated => { + major_text = Label::new("Not Authenticated"); + show_minor_text = false; + Some(PLEASE_AUTHENTICATE.to_string()) + } + SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), + SemanticIndexStatus::Indexing { + remaining_files, + rate_limit_expiry, + } => { + if remaining_files == 0 { + Some("Indexing...".to_string()) + } else { + if let Some(rate_limit_expiry) = rate_limit_expiry { + let remaining_seconds = + rate_limit_expiry.duration_since(Instant::now()); + if remaining_seconds > Duration::from_secs(0) { + Some(format!( + "Remaining files to index (rate limit resets in {}s): {}", + remaining_seconds.as_secs(), + remaining_files + )) + } else { + Some(format!("Remaining files to index: {}", remaining_files)) } + } else { + Some(format!("Remaining files to index: {}", remaining_files)) + } + } + } + SemanticIndexStatus::NotIndexed => None, + } }); let major_text = div().justify_center().max_w_96().child(major_text); From 4f25df6ce263b5ea770f2a51d1534b7dd5dc81bf Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 16 Jan 2024 11:22:14 -0500 Subject: [PATCH 226/334] Prevent div background/content/border from interleaving at same z-index Co-Authored-By: Antonio Scandurra --- crates/gpui/src/style.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 9f46d8dab6fd6214b0f22cf0b4e616300d595e8a..8fdb926b27ebc8e1ab6b4531a5902e0395f53f8b 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -386,7 +386,7 @@ impl Style { let background_color = self.background.as_ref().and_then(Fill::color); if background_color.map_or(false, |color| !color.is_transparent()) { - cx.with_z_index(1, |cx| { + cx.with_z_index(0, |cx| { let mut border_color = background_color.unwrap_or_default(); border_color.a = 0.; cx.paint_quad(quad( @@ -399,12 +399,12 @@ impl Style { }); } - cx.with_z_index(2, |cx| { + cx.with_z_index(0, |cx| { continuation(cx); }); if self.is_border_visible() { - cx.with_z_index(3, |cx| { + cx.with_z_index(0, |cx| { let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size); let border_widths = self.border_widths.to_pixels(rem_size); let max_border_width = border_widths.max(); From 60b79ef2ea90f294ee905b24612d3873da96a889 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 16 Jan 2024 11:23:28 -0500 Subject: [PATCH 227/334] Prevent content mask breaks from having the same z-index Co-Authored-By: Antonio Scandurra --- crates/gpui/src/window.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 869d6b18268cc64bc18f9187dd34e8032237d28b..0269ccfb6c8ef55df1696157302f6156e041f744 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -315,6 +315,7 @@ pub(crate) struct Frame { pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, pub(crate) z_index_stack: StackingOrder, pub(crate) next_stacking_order_id: u32, + next_root_z_index: u8, content_mask_stack: Vec>, element_offset_stack: Vec>, requested_input_handler: Option, @@ -337,6 +338,7 @@ impl Frame { depth_map: Vec::new(), z_index_stack: StackingOrder::default(), next_stacking_order_id: 0, + next_root_z_index: 0, content_mask_stack: Vec::new(), element_offset_stack: Vec::new(), requested_input_handler: None, @@ -354,6 +356,7 @@ impl Frame { self.dispatch_tree.clear(); self.depth_map.clear(); self.next_stacking_order_id = 0; + self.next_root_z_index = 0; self.reused_views.clear(); self.scene.clear(); self.requested_input_handler.take(); @@ -2450,8 +2453,13 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { }; let new_stacking_order_id = post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); + let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index); let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack); self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; + self.window_mut() + .next_frame + .z_index_stack + .push(new_root_z_index); self.window_mut().next_frame.content_mask_stack.push(mask); let result = f(self); self.window_mut().next_frame.content_mask_stack.pop(); From 52267a5dec084535e47fcfc9abd2a5d3fe03dd93 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 10:06:48 -0800 Subject: [PATCH 228/334] Adjust project search behavior to be isolated to a pane --- crates/search/src/project_search.rs | 76 ++++++++++++++++++----------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8897ae4bcfcd2ec2f14266dac81d97e8cf161985..a059b43f35cb939b504aea2253ea5dd7403dfadb 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -54,14 +54,10 @@ actions!( [SearchInNew, ToggleFocus, NextField, ToggleFilters] ); -#[derive(Default)] -struct ActiveSearches(HashMap, WeakView>); - #[derive(Default)] struct ActiveSettings(HashMap, ProjectSearchSettings>); pub fn init(cx: &mut AppContext) { - cx.set_global(ActiveSearches::default()); cx.set_global(ActiveSettings::default()); cx.observe_new_views(|workspace: &mut Workspace, _cx| { workspace @@ -943,25 +939,19 @@ impl ProjectSearchView { }); } - // Re-activate the most recently activated search or the most recent if it has been closed. + // Re-activate the most recently activated search in this pane or the most recent if it has been closed. // If no search exists in the workspace, create a new one. fn deploy_search( workspace: &mut Workspace, _: &workspace::DeploySearch, cx: &mut ViewContext, ) { - let active_search = cx - .global::() - .0 - .get(&workspace.project().downgrade()); - let existing = active_search - .and_then(|active_search| { - workspace - .items_of_type::(cx) - .filter(|search| &search.downgrade() == active_search) - .last() - }) - .or_else(|| workspace.item_of_type::(cx)); + let existing = workspace + .active_pane() + .read(cx) + .items() + .find_map(|item| item.downcast::()); + Self::existing_or_new_search(workspace, existing, cx) } @@ -979,11 +969,6 @@ impl ProjectSearchView { existing: Option>, cx: &mut ViewContext, ) { - // Clean up entries for dropped projects - cx.update_global(|state: &mut ActiveSearches, _cx| { - state.0.retain(|project, _| project.is_upgradable()) - }); - let query = workspace.active_item(cx).and_then(|item| { let editor = item.act_as::(cx)?; let query = editor.query_suggestion(cx); @@ -1015,6 +1000,7 @@ impl ProjectSearchView { workspace.add_item(Box::new(view.clone()), cx); view }; + search.update(cx, |search, cx| { if let Some(query) = query { search.set_query(&query, cx); @@ -3113,6 +3099,7 @@ pub mod tests { async fn test_deploy_search_with_multiple_panes(cx: &mut TestAppContext) { init_test(cx); + // Setup 2 panes, both with a file open and one with a project search. let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( "/dir", @@ -3171,6 +3158,8 @@ pub mod tests { } }) .unwrap(); + + // Add a project search item to the second pane window .update(cx, { let search_bar = search_bar.clone(); @@ -3190,6 +3179,8 @@ pub mod tests { cx.run_until_parked(); assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 2); assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1); + + // Focus the first pane window .update(cx, |workspace, cx| { assert_eq!(workspace.active_pane(), &second_pane); @@ -3208,20 +3199,47 @@ pub mod tests { assert_eq!(second_pane.read(cx).items_len(), 2); }) .unwrap(); + + // Deploy a new search cx.dispatch_action(window.into(), DeploySearch); - // We should have same # of items in workspace, the only difference being that - // the search we've deployed previously should now be focused. + // Both panes should now have a project search in them window .update(cx, |workspace, cx| { - assert_eq!(workspace.active_pane(), &second_pane); - second_pane.update(cx, |this, _| { + assert_eq!(workspace.active_pane(), &first_pane); + first_pane.update(cx, |this, _| { assert_eq!(this.active_item_index(), 1); assert_eq!(this.items_len(), 2); }); - first_pane.update(cx, |this, cx| { + second_pane.update(cx, |this, cx| { assert!(!cx.focus_handle().contains_focused(cx)); - assert_eq!(this.items_len(), 1); + assert_eq!(this.items_len(), 2); + }); + }) + .unwrap(); + + // Focus the second pane's non-search item + window + .update(cx, |_workspace, cx| { + second_pane.update(cx, |pane, cx| pane.activate_next_item(true, cx)); + }) + .unwrap(); + + // Deploy a new search + cx.dispatch_action(window.into(), DeploySearch); + + // The project search view should now be focused in the second pane + // And the number of items should be unchanged. + window + .update(cx, |_workspace, cx| { + second_pane.update(cx, |pane, _cx| { + assert!(pane + .active_item() + .unwrap() + .downcast::() + .is_some()); + + assert_eq!(pane.items_len(), 2); }); }) .unwrap(); @@ -3231,7 +3249,7 @@ pub mod tests { cx.update(|cx| { let settings = SettingsStore::test(cx); cx.set_global(settings); - cx.set_global(ActiveSearches::default()); + SemanticIndexSettings::register(cx); theme::init(theme::LoadThemes::JustBase, cx); From 36ff35fcca754d101530c5dd5fd22a09f4d1a734 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 10:30:21 -0800 Subject: [PATCH 229/334] Change name of deploy action to indicate what scope it operates at --- assets/keymaps/default.json | 2 +- crates/workspace/src/pane.rs | 1 + crates/workspace/src/workspace.rs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 1372f7a4157da475aa8ccbff8aeb940c85d2ea79..f18cc2a111ec432a283f9f08f3fd1acecdddda52 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -402,7 +402,7 @@ "cmd-r": "workspace::ToggleRightDock", "cmd-j": "workspace::ToggleBottomDock", "alt-cmd-y": "workspace::CloseAllDocks", - "cmd-shift-f": "workspace::DeploySearch", + "cmd-shift-f": "pane::DeploySearch", "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", "cmd-t": "project_symbols::Toggle", diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1b95671398a613a01f41590bdce20e0a846592d0..5ed68580877a7347f1df00e355b811054ba7fdb0 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -99,6 +99,7 @@ actions!( CloseItemsToTheLeft, CloseItemsToTheRight, GoBack, + DeploySearch, GoForward, ReopenClosedItem, SplitLeft, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index efd2c52989edb51cff2559382f0ec62a2ce2702e..4a8e87db1004345f3a4138c435644cd1722ee364 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -108,7 +108,6 @@ actions!( NewCenterTerminal, ToggleTerminalFocus, NewSearch, - DeploySearch, Feedback, Restart, Welcome, From ca4a8b22261a54e8e6e5db3c2e6e7fb5f7dbcb45 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 16 Jan 2024 14:05:05 -0500 Subject: [PATCH 230/334] Rework `Avatar` indicator to be more general-purpose (#4073) This PR reworks the way we add indicators to `Avatar`s to make them more general-purpose. Previously we had logic specific to the availability indicator embedded in the `Avatar` component, which made it unwieldy to repurpose for something else. Now the `indicator` is just a slot that we can put anything into. Release Notes: - N/A --- crates/collab_ui/src/collab_panel.rs | 84 ++++++----- crates/ui/src/components/avatar.rs | 138 +----------------- crates/ui/src/components/avatar/avatar.rs | 122 ++++++++++++++++ .../avatar/avatar_availability_indicator.rs | 48 ++++++ crates/ui/src/components/stories/avatar.rs | 6 +- 5 files changed, 222 insertions(+), 176 deletions(-) create mode 100644 crates/ui/src/components/avatar/avatar.rs create mode 100644 crates/ui/src/components/avatar/avatar_availability_indicator.rs diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 9cb447153c1bc39b9d291a2e17020cae788b43ca..ec94838386029b69c9feeea4f1879ea792e56bc7 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -31,8 +31,8 @@ use smallvec::SmallVec; use std::{mem, sync::Arc}; use theme::{ActiveTheme, ThemeSettings}; use ui::{ - prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconName, IconSize, Label, - ListHeader, ListItem, Tooltip, + prelude::*, Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu, Icon, IconButton, + IconName, IconSize, Label, ListHeader, ListItem, Tooltip, }; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -2000,43 +2000,49 @@ impl CollabPanel { let busy = contact.busy || calling; let user_id = contact.user.id; let github_login = SharedString::from(contact.user.github_login.clone()); - let item = - ListItem::new(github_login.clone()) - .indent_level(1) - .indent_step_size(px(20.)) - .selected(is_selected) - .on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) - .child( - h_flex() - .w_full() - .justify_between() - .child(Label::new(github_login.clone())) - .when(calling, |el| { - el.child(Label::new("Calling").color(Color::Muted)) - }) - .when(!calling, |el| { - el.child( - IconButton::new("remove_contact", IconName::Close) - .icon_color(Color::Muted) - .visible_on_hover("") - .tooltip(|cx| Tooltip::text("Remove Contact", cx)) - .on_click(cx.listener({ - let github_login = github_login.clone(); - move |this, _, cx| { - this.remove_contact(user_id, &github_login, cx); - } - })), - ) - }), - ) - .start_slot( - // todo handle contacts with no avatar - Avatar::new(contact.user.avatar_uri.clone()) - .availability_indicator(if online { Some(!busy) } else { None }), - ) - .when(online && !busy, |el| { - el.on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) - }); + let item = ListItem::new(github_login.clone()) + .indent_level(1) + .indent_step_size(px(20.)) + .selected(is_selected) + .on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) + .child( + h_flex() + .w_full() + .justify_between() + .child(Label::new(github_login.clone())) + .when(calling, |el| { + el.child(Label::new("Calling").color(Color::Muted)) + }) + .when(!calling, |el| { + el.child( + IconButton::new("remove_contact", IconName::Close) + .icon_color(Color::Muted) + .visible_on_hover("") + .tooltip(|cx| Tooltip::text("Remove Contact", cx)) + .on_click(cx.listener({ + let github_login = github_login.clone(); + move |this, _, cx| { + this.remove_contact(user_id, &github_login, cx); + } + })), + ) + }), + ) + .start_slot( + // todo handle contacts with no avatar + Avatar::new(contact.user.avatar_uri.clone()) + .indicator::(if online { + Some(AvatarAvailabilityIndicator::new(match busy { + true => ui::Availability::Busy, + false => ui::Availability::Free, + })) + } else { + None + }), + ) + .when(online && !busy, |el| { + el.on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) + }); div() .id(github_login.clone()) diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index a97adb73b7d88ae0dfb1c60c25eff3942c5ad52d..b200828ce6cd5a4ac190faf65f8417e08844cf43 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -1,135 +1,5 @@ -use crate::prelude::*; -use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled}; +mod avatar; +mod avatar_availability_indicator; -/// The shape of an [`Avatar`]. -#[derive(Debug, Default, PartialEq, Clone)] -pub enum AvatarShape { - /// The avatar is shown in a circle. - #[default] - Circle, - /// The avatar is shown in a rectangle with rounded corners. - RoundedRectangle, -} - -/// An element that renders a user avatar with customizable appearance options. -/// -/// # Examples -/// -/// ``` -/// use ui::{Avatar, AvatarShape}; -/// -/// Avatar::new("path/to/image.png") -/// .shape(AvatarShape::Circle) -/// .grayscale(true) -/// .border_color(gpui::red()); -/// ``` -#[derive(IntoElement)] -pub struct Avatar { - image: Img, - size: Option, - border_color: Option, - is_available: Option, -} - -impl RenderOnce for Avatar { - fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { - if self.image.style().corner_radii.top_left.is_none() { - self = self.shape(AvatarShape::Circle); - } - - let size = self.size.unwrap_or_else(|| cx.rem_size()); - - div() - .size(size + px(2.)) - .map(|mut div| { - div.style().corner_radii = self.image.style().corner_radii.clone(); - div - }) - .when_some(self.border_color, |this, color| { - this.border().border_color(color) - }) - .child( - self.image - .size(size) - .bg(cx.theme().colors().ghost_element_background), - ) - .children(self.is_available.map(|is_free| { - // HACK: non-integer sizes result in oval indicators. - let indicator_size = (size * 0.4).round(); - - div() - .absolute() - .z_index(1) - .bg(if is_free { - cx.theme().status().created - } else { - cx.theme().status().deleted - }) - .size(indicator_size) - .rounded(indicator_size) - .bottom_0() - .right_0() - })) - } -} - -impl Avatar { - pub fn new(src: impl Into) -> Self { - Avatar { - image: img(src), - is_available: None, - border_color: None, - size: None, - } - } - - /// Sets the shape of the avatar image. - /// - /// This method allows the shape of the avatar to be specified using a [`Shape`]. - /// It modifies the corner radius of the image to match the specified shape. - /// - /// # Examples - /// - /// ``` - /// use ui::{Avatar, AvatarShape}; - /// - /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); - /// ``` - pub fn shape(mut self, shape: AvatarShape) -> Self { - self.image = match shape { - AvatarShape::Circle => self.image.rounded_full(), - AvatarShape::RoundedRectangle => self.image.rounded_md(), - }; - self - } - - /// Applies a grayscale filter to the avatar image. - /// - /// # Examples - /// - /// ``` - /// use ui::{Avatar, AvatarShape}; - /// - /// let avatar = Avatar::new("path/to/image.png").grayscale(true); - /// ``` - pub fn grayscale(mut self, grayscale: bool) -> Self { - self.image = self.image.grayscale(grayscale); - self - } - - pub fn border_color(mut self, color: impl Into) -> Self { - self.border_color = Some(color.into()); - self - } - - pub fn availability_indicator(mut self, is_available: impl Into>) -> Self { - self.is_available = is_available.into(); - self - } - - /// Size overrides the avatar size. By default they are 1rem. - pub fn size(mut self, size: impl Into>) -> Self { - self.size = size.into(); - self - } -} +pub use avatar::*; +pub use avatar_availability_indicator::*; diff --git a/crates/ui/src/components/avatar/avatar.rs b/crates/ui/src/components/avatar/avatar.rs new file mode 100644 index 0000000000000000000000000000000000000000..22ba255a60f12742e173f016886ce25b404d2c0f --- /dev/null +++ b/crates/ui/src/components/avatar/avatar.rs @@ -0,0 +1,122 @@ +use crate::prelude::*; +use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled}; + +/// The shape of an [`Avatar`]. +#[derive(Debug, Default, PartialEq, Clone)] +pub enum AvatarShape { + /// The avatar is shown in a circle. + #[default] + Circle, + /// The avatar is shown in a rectangle with rounded corners. + RoundedRectangle, +} + +/// An element that renders a user avatar with customizable appearance options. +/// +/// # Examples +/// +/// ``` +/// use ui::{Avatar, AvatarShape}; +/// +/// Avatar::new("path/to/image.png") +/// .shape(AvatarShape::Circle) +/// .grayscale(true) +/// .border_color(gpui::red()); +/// ``` +#[derive(IntoElement)] +pub struct Avatar { + image: Img, + size: Option, + border_color: Option, + indicator: Option, +} + +impl Avatar { + pub fn new(src: impl Into) -> Self { + Avatar { + image: img(src), + size: None, + border_color: None, + indicator: None, + } + } + + /// Sets the shape of the avatar image. + /// + /// This method allows the shape of the avatar to be specified using a [`Shape`]. + /// It modifies the corner radius of the image to match the specified shape. + /// + /// # Examples + /// + /// ``` + /// use ui::{Avatar, AvatarShape}; + /// + /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); + /// ``` + pub fn shape(mut self, shape: AvatarShape) -> Self { + self.image = match shape { + AvatarShape::Circle => self.image.rounded_full(), + AvatarShape::RoundedRectangle => self.image.rounded_md(), + }; + self + } + + /// Applies a grayscale filter to the avatar image. + /// + /// # Examples + /// + /// ``` + /// use ui::{Avatar, AvatarShape}; + /// + /// let avatar = Avatar::new("path/to/image.png").grayscale(true); + /// ``` + pub fn grayscale(mut self, grayscale: bool) -> Self { + self.image = self.image.grayscale(grayscale); + self + } + + pub fn border_color(mut self, color: impl Into) -> Self { + self.border_color = Some(color.into()); + self + } + + /// Size overrides the avatar size. By default they are 1rem. + pub fn size(mut self, size: impl Into>) -> Self { + self.size = size.into(); + self + } + + pub fn indicator(mut self, indicator: impl Into>) -> Self { + self.indicator = indicator.into().map(IntoElement::into_any_element); + self + } +} + +impl RenderOnce for Avatar { + fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { + if self.image.style().corner_radii.top_left.is_none() { + self = self.shape(AvatarShape::Circle); + } + + let size = self.size.unwrap_or_else(|| cx.rem_size()); + + div() + .size(size + px(2.)) + .map(|mut div| { + div.style().corner_radii = self.image.style().corner_radii.clone(); + div + }) + .when_some(self.border_color, |this, color| { + this.border().border_color(color) + }) + .child( + self.image + .size(size) + .bg(cx.theme().colors().ghost_element_background), + ) + .children( + self.indicator + .map(|indicator| div().z_index(1).child(indicator)), + ) + } +} diff --git a/crates/ui/src/components/avatar/avatar_availability_indicator.rs b/crates/ui/src/components/avatar/avatar_availability_indicator.rs new file mode 100644 index 0000000000000000000000000000000000000000..3a033cd3959c5d63af5960ba839fb37950fbec57 --- /dev/null +++ b/crates/ui/src/components/avatar/avatar_availability_indicator.rs @@ -0,0 +1,48 @@ +use crate::prelude::*; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub enum Availability { + Free, + Busy, +} + +#[derive(IntoElement)] +pub struct AvatarAvailabilityIndicator { + availability: Availability, + avatar_size: Option, +} + +impl AvatarAvailabilityIndicator { + pub fn new(availability: Availability) -> Self { + Self { + availability, + avatar_size: None, + } + } + + /// Sets the size of the [`Avatar`] this indicator appears on. + pub fn avatar_size(mut self, size: impl Into>) -> Self { + self.avatar_size = size.into(); + self + } +} + +impl RenderOnce for AvatarAvailabilityIndicator { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let avatar_size = self.avatar_size.unwrap_or_else(|| cx.rem_size()); + + // HACK: non-integer sizes result in oval indicators. + let indicator_size = (avatar_size * 0.4).round(); + + div() + .absolute() + .bottom_0() + .right_0() + .size(indicator_size) + .rounded(indicator_size) + .bg(match self.availability { + Availability::Free => cx.theme().status().created, + Availability::Busy => cx.theme().status().deleted, + }) + } +} diff --git a/crates/ui/src/components/stories/avatar.rs b/crates/ui/src/components/stories/avatar.rs index e447486d69ca4a22a10e3d8975546b4ef661cf0d..c7aae8a73cde4098dca874754c40dd994a1da934 100644 --- a/crates/ui/src/components/stories/avatar.rs +++ b/crates/ui/src/components/stories/avatar.rs @@ -1,8 +1,8 @@ use gpui::Render; use story::Story; -use crate::prelude::*; use crate::Avatar; +use crate::{prelude::*, Availability, AvatarAvailabilityIndicator}; pub struct AvatarStory; @@ -19,11 +19,11 @@ impl Render for AvatarStory { )) .child( Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .availability_indicator(true), + .indicator(AvatarAvailabilityIndicator::new(Availability::Free)), ) .child( Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .availability_indicator(false), + .indicator(AvatarAvailabilityIndicator::new(Availability::Busy)), ) } } From f011953484ae54eed421f6093fce65dd41cd9206 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:12:16 +0100 Subject: [PATCH 231/334] Rename all_font_families to all_font_names --- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/mac/text_system.rs | 18 +++++++++++------- crates/gpui/src/text_system.rs | 5 ++--- crates/theme/src/settings.rs | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 5a2335919ebe6ff9b2277e02d4f6f55b4dc9a80c..f165cd9c2b5aa71a3a982a5e23faafaca2b8bf3a 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -192,7 +192,7 @@ pub trait PlatformDispatcher: Send + Sync { pub trait PlatformTextSystem: Send + Sync { fn add_fonts(&self, fonts: &[Arc>]) -> Result<()>; - fn all_font_families(&self) -> Vec; + fn all_font_names(&self) -> Vec; fn font_id(&self, descriptor: &Font) -> Result; fn font_metrics(&self, font_id: FontId) -> FontMetrics; fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result>; diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 68f4a63326757f8a87217a133aef005d0c8e5c86..06179e126b6b779a56d25b1bf154eef37162a646 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -5,7 +5,7 @@ use crate::{ }; use anyhow::anyhow; use cocoa::appkit::{CGFloat, CGPoint}; -use collections::HashMap; +use collections::{BTreeSet, HashMap}; use core_foundation::{ array::CFIndex, attributed_string::{CFAttributedStringRef, CFMutableAttributedString}, @@ -78,12 +78,16 @@ impl PlatformTextSystem for MacTextSystem { self.0.write().add_fonts(fonts) } - fn all_font_families(&self) -> Vec { - self.0 - .read() - .system_source - .all_families() - .expect("core text should never return an error") + fn all_font_names(&self) -> Vec { + let collection = core_text::font_collection::create_for_all_families(); + let Some(descriptors) = collection.get_descriptors() else { + return vec![]; + }; + let mut names = BTreeSet::new(); + for descriptor in descriptors.into_iter() { + names.insert(descriptor.display_name()); + } + names.into_iter().collect() } fn font_id(&self, font: &Font) -> Result { diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 24438d8c819527a3e8dd869f9e1dac179d9dd618..34470aff021297d9b8a12025e5348550cd6111c5 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -65,8 +65,8 @@ impl TextSystem { } } - pub fn all_font_families(&self) -> Vec { - let mut families = self.platform_text_system.all_font_families(); + pub fn all_font_names(&self) -> Vec { + let mut families = self.platform_text_system.all_font_names(); families.append( &mut self .fallback_font_stack @@ -101,7 +101,6 @@ impl TextSystem { if let Ok(font_id) = self.font_id(font) { return font_id; } - for fallback in &self.fallback_font_stack { if let Ok(font_id) = self.font_id(fallback) { return font_id; diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index efc62ed59c429b97420fc276d1f2e2eca6c841cc..90c61ce269019e405ca825fb1bd6d30597fd54cf 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -205,7 +205,7 @@ impl settings::Settings for ThemeSettings { let available_fonts = cx .text_system() - .all_font_families() + .all_font_names() .into_iter() .map(Value::String) .collect(); From ff67d9dea033169bef0a92d8c00a7156fc3936c6 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 16 Jan 2024 20:19:58 +0100 Subject: [PATCH 232/334] Add font name completions to ui_font_family and terminal::font_family --- Cargo.lock | 1 + crates/terminal/Cargo.toml | 1 + crates/terminal/src/terminal_settings.rs | 41 +++++++++++++++++++++++- crates/theme/src/settings.rs | 4 +++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7b1fe5144c5b95c0adc0c02a2c28dd3cec44fa09..42416c2f2458f52a2ccef25e9789e144d73ad1fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7775,6 +7775,7 @@ dependencies = [ "schemars", "serde", "serde_derive", + "serde_json", "settings", "shellexpand", "smallvec", diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index ce2a158e2076a852addcef71afc2644cdd903531..4dc253fd5343b8530fed48b6e6f17400dcb82bca 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -33,6 +33,7 @@ thiserror.workspace = true lazy_static.workspace = true serde.workspace = true serde_derive.workspace = true +serde_json.workspace = true [dev-dependencies] rand.workspace = true diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 14cff3b5a690a6bc370579604305ff5a1217a351..b9b79c9c6b6996e674f1af25485343a093b3fe85 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -1,6 +1,12 @@ use gpui::{px, AbsoluteLength, AppContext, FontFeatures, Pixels}; -use schemars::JsonSchema; +use schemars::{ + gen::SchemaGenerator, + schema::{InstanceType, RootSchema, Schema, SchemaObject}, + JsonSchema, +}; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value; +use settings::SettingsJsonSchemaParams; use std::{collections::HashMap, path::PathBuf}; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -153,6 +159,39 @@ impl settings::Settings for TerminalSettings { ) -> anyhow::Result { Self::load_via_json_merge(default_value, user_values) } + fn json_schema( + generator: &mut SchemaGenerator, + _: &SettingsJsonSchemaParams, + cx: &AppContext, + ) -> RootSchema { + let mut root_schema = generator.root_schema_for::(); + let available_fonts = cx + .text_system() + .all_font_names() + .into_iter() + .map(Value::String) + .collect(); + let fonts_schema = SchemaObject { + instance_type: Some(InstanceType::String.into()), + enum_values: Some(available_fonts), + ..Default::default() + }; + root_schema + .definitions + .extend([("FontFamilies".into(), fonts_schema.into())]); + root_schema + .schema + .object + .as_mut() + .unwrap() + .properties + .extend([( + "font_family".to_owned(), + Schema::new_ref("#/definitions/FontFamilies".into()), + )]); + + root_schema + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 90c61ce269019e405ca825fb1bd6d30597fd54cf..bced187411594b5979245103c6f99c1aedcd7fa5 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -234,6 +234,10 @@ impl settings::Settings for ThemeSettings { "buffer_font_family".to_owned(), Schema::new_ref("#/definitions/FontFamilies".into()), ), + ( + "ui_font_family".to_owned(), + Schema::new_ref("#/definitions/FontFamilies".into()), + ), ]); root_schema From 9903b7ae6ecd02f0ebbfb138ab0d904adb83d7ec Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 16 Jan 2024 15:17:29 -0500 Subject: [PATCH 233/334] Add color ribbon for local player (#4075) This PR adds a color ribbon for the local player in the current call. This fixes the alignment of the local user's avatar so that it lines up with the rest of the collaborators in the call: Screenshot 2024-01-16 at 2 56 04 PM Release Notes: - Added a color ribbon for the local player when in a call. --- crates/collab_ui/src/collab_titlebar_item.rs | 24 +++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 432f8f6cd242b3b7bbeb53464de98980b4079e47..72b16d49185f6928ca9195c6159e7395add273d2 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -1,7 +1,7 @@ use crate::face_pile::FacePile; use auto_update::AutoUpdateStatus; use call::{ActiveCall, ParticipantLocation, Room}; -use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; +use client::{proto::PeerId, Client, User, UserStore}; use gpui::{ actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Element, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render, @@ -12,7 +12,7 @@ use project::{Project, RepositoryEntry}; use recent_projects::RecentProjects; use rpc::proto; use std::sync::Arc; -use theme::{ActiveTheme, PlayerColors}; +use theme::ActiveTheme; use ui::{ h_flex, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, Tooltip, @@ -97,7 +97,7 @@ impl Render for CollabTitlebarItem { room.remote_participants().values().collect::>(); remote_participants.sort_by_key(|p| p.participant_index.0); - this.children(self.render_collaborator( + let current_user_face_pile = self.render_collaborator( ¤t_user, peer_id, true, @@ -107,7 +107,13 @@ impl Render for CollabTitlebarItem { project_id, ¤t_user, cx, - )) + ); + + this.children(current_user_face_pile.map(|face_pile| { + v_flex() + .child(face_pile) + .child(render_color_ribbon(player_colors.local().cursor)) + })) .children( remote_participants.iter().filter_map(|collaborator| { let is_present = project_id.map_or(false, |project_id| { @@ -132,8 +138,11 @@ impl Render for CollabTitlebarItem { .id(("collaborator", collaborator.user.id)) .child(face_pile) .child(render_color_ribbon( - collaborator.participant_index, - player_colors, + player_colors + .color_for_participant( + collaborator.participant_index.0, + ) + .cursor, )) .cursor_pointer() .on_click({ @@ -312,8 +321,7 @@ impl Render for CollabTitlebarItem { } } -fn render_color_ribbon(participant_index: ParticipantIndex, colors: &PlayerColors) -> gpui::Canvas { - let color = colors.color_for_participant(participant_index.0).cursor; +fn render_color_ribbon(color: Hsla) -> gpui::Canvas { canvas(move |bounds, cx| { let height = bounds.size.height; let horizontal_offset = height; From 2e03c848e31257ddae10dcbe6d5be793d6394966 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 16 Jan 2024 16:18:06 -0500 Subject: [PATCH 234/334] Add dedicated indicator for showing a muted call participant (#4076) This PR improves the muted indicators to make it clearer when a call participant is muted. Previously we used a red border color to denote when a participant was muted. Now we render an indicator with an icon to more clearly indicate the participant's muted status: Screenshot 2024-01-16 at 4 05 15 PM Hovering over the indicator will display a tooltip for further explanation: Screenshot 2024-01-16 at 4 05 25 PM This change also paves the way for denoting the deafened status for call participants. Release Notes: - Improved the mute indicator for call participants. --- crates/collab_ui/src/collab_titlebar_item.rs | 20 +++--- crates/ui/src/components/avatar.rs | 2 + crates/ui/src/components/avatar/avatar.rs | 1 + .../avatar/avatar_audio_status_indicator.rs | 65 +++++++++++++++++++ crates/ui/src/components/icon.rs | 2 + crates/ui/src/components/stories/avatar.rs | 12 +++- 6 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 crates/ui/src/components/avatar/avatar_audio_status_indicator.rs diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 72b16d49185f6928ca9195c6159e7395add273d2..9f11870d983a20013666ad61c5a6cd1bd30bdba3 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -14,8 +14,8 @@ use rpc::proto; use std::sync::Arc; use theme::ActiveTheme; use ui::{ - h_flex, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, - IconButton, IconName, TintColor, Tooltip, + h_flex, popover_menu, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, + ButtonStyle, ContextMenu, Icon, IconButton, IconName, TintColor, Tooltip, }; use util::ResultExt; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; @@ -486,12 +486,16 @@ impl CollabTitlebarItem { .child( Avatar::new(user.avatar_uri.clone()) .grayscale(!is_present) - .border_color(if is_speaking { - cx.theme().status().info_border - } else if is_muted { - cx.theme().status().error_border - } else { - Hsla::default() + .when(is_speaking, |avatar| { + avatar.border_color(cx.theme().status().info_border) + }) + .when(is_muted, |avatar| { + avatar.indicator( + AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted).tooltip({ + let github_login = user.github_login.clone(); + move |cx| Tooltip::text(format!("{} is muted", github_login), cx) + }), + ) }), ) .children(followers.iter().filter_map(|follower_peer_id| { diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index b200828ce6cd5a4ac190faf65f8417e08844cf43..6c2d88916e7fe47f9fbcfe86b7d88ebd43e4b62f 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -1,5 +1,7 @@ mod avatar; +mod avatar_audio_status_indicator; mod avatar_availability_indicator; pub use avatar::*; +pub use avatar_audio_status_indicator::*; pub use avatar_availability_indicator::*; diff --git a/crates/ui/src/components/avatar/avatar.rs b/crates/ui/src/components/avatar/avatar.rs index 22ba255a60f12742e173f016886ce25b404d2c0f..5154d90bd2e38718d003ad167a4eabb7a5852479 100644 --- a/crates/ui/src/components/avatar/avatar.rs +++ b/crates/ui/src/components/avatar/avatar.rs @@ -1,4 +1,5 @@ use crate::prelude::*; + use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled}; /// The shape of an [`Avatar`]. diff --git a/crates/ui/src/components/avatar/avatar_audio_status_indicator.rs b/crates/ui/src/components/avatar/avatar_audio_status_indicator.rs new file mode 100644 index 0000000000000000000000000000000000000000..55f98d3db3b8a49f827ffac0d13d69c00dfc5d75 --- /dev/null +++ b/crates/ui/src/components/avatar/avatar_audio_status_indicator.rs @@ -0,0 +1,65 @@ +use gpui::AnyView; + +use crate::prelude::*; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub enum AudioStatus { + Muted, + Deafened, +} + +#[derive(IntoElement)] +pub struct AvatarAudioStatusIndicator { + audio_status: AudioStatus, + tooltip: Option AnyView>>, +} + +impl AvatarAudioStatusIndicator { + pub fn new(audio_status: AudioStatus) -> Self { + Self { + audio_status, + tooltip: None, + } + } + + pub fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { + self.tooltip = Some(Box::new(tooltip)); + self + } +} + +impl RenderOnce for AvatarAudioStatusIndicator { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let icon_size = IconSize::Indicator; + + let width_in_px = icon_size.rems() * cx.rem_size(); + let padding_x = px(4.); + + div() + .absolute() + .bottom(rems(-1. / 16.)) + .right(rems(-4. / 16.)) + .w(width_in_px + padding_x) + .h(icon_size.rems()) + .child( + h_flex() + .id("muted-indicator") + .justify_center() + .px(padding_x) + .py(px(2.)) + .bg(cx.theme().status().error_background) + .rounded_md() + .child( + Icon::new(match self.audio_status { + AudioStatus::Muted => IconName::MicMute, + AudioStatus::Deafened => IconName::AudioOff, + }) + .size(icon_size) + .color(Color::Error), + ) + .when_some(self.tooltip, |this, tooltip| { + this.tooltip(move |cx| tooltip(cx)) + }), + ) + } +} diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 908e76ef918b56aefff6949e86ea6473a272253d..bdc691dc9a26e178ad65eaaeda85caa7973dd526 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -5,6 +5,7 @@ use crate::prelude::*; #[derive(Default, PartialEq, Copy, Clone)] pub enum IconSize { + Indicator, XSmall, Small, #[default] @@ -14,6 +15,7 @@ pub enum IconSize { impl IconSize { pub fn rems(self) -> Rems { match self { + IconSize::Indicator => rems(10. / 16.), IconSize::XSmall => rems(12. / 16.), IconSize::Small => rems(14. / 16.), IconSize::Medium => rems(16. / 16.), diff --git a/crates/ui/src/components/stories/avatar.rs b/crates/ui/src/components/stories/avatar.rs index c7aae8a73cde4098dca874754c40dd994a1da934..c3409b1ca81dfcf412111eb832a61b83c1a768bc 100644 --- a/crates/ui/src/components/stories/avatar.rs +++ b/crates/ui/src/components/stories/avatar.rs @@ -1,8 +1,8 @@ use gpui::Render; use story::Story; -use crate::Avatar; -use crate::{prelude::*, Availability, AvatarAvailabilityIndicator}; +use crate::{prelude::*, AudioStatus, Availability, AvatarAvailabilityIndicator}; +use crate::{Avatar, AvatarAudioStatusIndicator}; pub struct AvatarStory; @@ -25,5 +25,13 @@ impl Render for AvatarStory { Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") .indicator(AvatarAvailabilityIndicator::new(Availability::Busy)), ) + .child( + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)), + ) + .child( + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)), + ) } } From 4e8ad363f1cb419fa747483db5119b862cbe8593 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 16 Jan 2024 17:09:28 -0500 Subject: [PATCH 235/334] Increase border width used to indicate speaking (#4077) This PR increases the width of the border that we use to indicate when a call participant is speaking. This should make it more apparent in the UI when someone is speaking. Release Notes: - Increased the width of the ring used to indicate when someone is speaking in a call. --- crates/collab_ui/src/collab_titlebar_item.rs | 8 ++- crates/gpui_macros/src/style_helpers.rs | 14 ++++- crates/ui/src/components/avatar/avatar.rs | 15 +++-- crates/ui/src/components/stories/avatar.rs | 64 ++++++++++++++------ 4 files changed, 75 insertions(+), 26 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 9f11870d983a20013666ad61c5a6cd1bd30bdba3..077e08fac78960449824ddb3473e74f916b7577c 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -486,8 +486,12 @@ impl CollabTitlebarItem { .child( Avatar::new(user.avatar_uri.clone()) .grayscale(!is_present) - .when(is_speaking, |avatar| { - avatar.border_color(cx.theme().status().info_border) + .border_color(if is_speaking { + cx.theme().status().info_border + } else { + // We draw the border in a transparent color rather to avoid + // the layout shift that would come with adding/removing the border. + gpui::transparent_black() }) .when(is_muted, |avatar| { avatar.indicator( diff --git a/crates/gpui_macros/src/style_helpers.rs b/crates/gpui_macros/src/style_helpers.rs index b86bb2dfa60b7a97394cafe3fc4e1aaf22b87302..00d3672033fd9042d72ccff5c93b9e8b1f41055e 100644 --- a/crates/gpui_macros/src/style_helpers.rs +++ b/crates/gpui_macros/src/style_helpers.rs @@ -85,6 +85,18 @@ fn generate_methods() -> Vec { } for (prefix, fields, prefix_doc_string) in border_prefixes() { + methods.push(generate_custom_value_setter( + // The plain method names (e.g., `border`, `border_t`, `border_r`, etc.) are special-cased + // versions of the 1px variants. This better matches Tailwind, but breaks our existing + // convention of the suffix-less variant of the method being the one that accepts a custom value + // + // To work around this, we're assigning a `_width` suffix here. + &format!("{prefix}_width"), + quote! { AbsoluteLength }, + &fields, + prefix_doc_string, + )); + for (suffix, width_tokens, suffix_doc_string) in border_suffixes() { methods.push(generate_predefined_setter( prefix, @@ -141,7 +153,7 @@ fn generate_predefined_setter( } fn generate_custom_value_setter( - prefix: &'static str, + prefix: &str, length_type: TokenStream2, fields: &[TokenStream2], doc_string: &str, diff --git a/crates/ui/src/components/avatar/avatar.rs b/crates/ui/src/components/avatar/avatar.rs index 5154d90bd2e38718d003ad167a4eabb7a5852479..932cc9e243558fb78cbf6e15340974a0551699c9 100644 --- a/crates/ui/src/components/avatar/avatar.rs +++ b/crates/ui/src/components/avatar/avatar.rs @@ -99,20 +99,27 @@ impl RenderOnce for Avatar { self = self.shape(AvatarShape::Circle); } - let size = self.size.unwrap_or_else(|| cx.rem_size()); + let border_width = if self.border_color.is_some() { + px(2.) + } else { + px(0.) + }; + + let image_size = self.size.unwrap_or_else(|| cx.rem_size()); + let container_size = image_size + border_width * 2.; div() - .size(size + px(2.)) + .size(container_size) .map(|mut div| { div.style().corner_radii = self.image.style().corner_radii.clone(); div }) .when_some(self.border_color, |this, color| { - this.border().border_color(color) + this.border_width(border_width).border_color(color) }) .child( self.image - .size(size) + .size(image_size) .bg(cx.theme().colors().ghost_element_background), ) .children( diff --git a/crates/ui/src/components/stories/avatar.rs b/crates/ui/src/components/stories/avatar.rs index c3409b1ca81dfcf412111eb832a61b83c1a768bc..9da475b0d9be59d64580b4db416ed99afbb1e402 100644 --- a/crates/ui/src/components/stories/avatar.rs +++ b/crates/ui/src/components/stories/avatar.rs @@ -1,5 +1,5 @@ use gpui::Render; -use story::Story; +use story::{StoryContainer, StoryItem, StorySection}; use crate::{prelude::*, AudioStatus, Availability, AvatarAvailabilityIndicator}; use crate::{Avatar, AvatarAudioStatusIndicator}; @@ -7,31 +7,57 @@ use crate::{Avatar, AvatarAudioStatusIndicator}; pub struct AvatarStory; impl Render for AvatarStory { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - Story::container() - .child(Story::title_for::()) - .child(Story::label("Default")) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/1714999?v=4", - )) - .child(Avatar::new( - "https://avatars.githubusercontent.com/u/326587?v=4", - )) + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + StoryContainer::new("Avatar", "crates/ui/src/components/stories/avatar.rs") .child( - Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .indicator(AvatarAvailabilityIndicator::new(Availability::Free)), + StorySection::new() + .child(StoryItem::new( + "Default", + Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4"), + )) + .child(StoryItem::new( + "Default", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4"), + )), ) .child( - Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .indicator(AvatarAvailabilityIndicator::new(Availability::Busy)), + StorySection::new() + .child(StoryItem::new( + "With free availability indicator", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAvailabilityIndicator::new(Availability::Free)), + )) + .child(StoryItem::new( + "With busy availability indicator", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAvailabilityIndicator::new(Availability::Busy)), + )), ) .child( - Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)), + StorySection::new() + .child(StoryItem::new( + "With info border", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .border_color(cx.theme().status().info_border), + )) + .child(StoryItem::new( + "With error border", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .border_color(cx.theme().status().error_border), + )), ) .child( - Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") - .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)), + StorySection::new() + .child(StoryItem::new( + "With muted audio indicator", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)), + )) + .child(StoryItem::new( + "With deafened audio indicator", + Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4") + .indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)), + )), ) } } From cce3cf145cfb865bd1237873e6b30dc7bcb84d42 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 16 Jan 2024 15:18:58 -0700 Subject: [PATCH 236/334] Play guess who's to blame --- crates/gpui/src/platform/mac/metal_renderer.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 20e749a2f607fa96ec6fbdfc76574307648a621d..36ded7ba8ecac63007e1e682caef624ee9766e3f 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -14,7 +14,6 @@ use foreign_types::ForeignType; use media::core_video::CVMetalTextureCache; use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use objc::{self, msg_send, sel, sel_impl}; -use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); @@ -538,7 +537,7 @@ impl MetalRenderer { ); let mut prev_texture_id = None; - let mut sprites = SmallVec::<[_; 1]>::new(); + let mut sprites = Vec::new(); let mut paths_and_tiles = paths .iter() .map(|path| (path, tiles_by_path_id.get(&path.id).unwrap())) From 00682b8903a8553b5d34b4f502afe6c50c027f23 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 16 Jan 2024 18:31:00 -0500 Subject: [PATCH 237/334] Do not reset timer for each reported event --- crates/client/src/telemetry.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 32cf9efba230da83f68d568818d20a52bfc862dd..102362c422013773c44fa79d67ec6baa0bc8e3a7 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -457,20 +457,24 @@ impl Telemetry { return; } + if state.flush_events_task.is_none() { + let this = self.clone(); + let executor = self.executor.clone(); + state.flush_events_task = Some(self.executor.spawn(async move { + executor.timer(FLUSH_DEBOUNCE_INTERVAL).await; + this.flush_events(); + })); + } + let signed_in = state.metrics_id.is_some(); state.events_queue.push(EventWrapper { signed_in, event }); + dbg!(&state.events_queue.len()); + if state.installation_id.is_some() { if state.events_queue.len() >= MAX_QUEUE_LEN { drop(state); self.flush_events(); - } else { - let this = self.clone(); - let executor = self.executor.clone(); - state.flush_events_task = Some(self.executor.spawn(async move { - executor.timer(FLUSH_DEBOUNCE_INTERVAL).await; - this.flush_events(); - })); } } } @@ -534,6 +538,7 @@ impl Telemetry { release_channel: state.release_channel, events, }; + dbg!("flush", &request_body); json_bytes.clear(); serde_json::to_writer(&mut json_bytes, &request_body)?; } From 0c59f510d2d539c6a5196b414d2dff57c7654dec Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 16 Jan 2024 18:33:43 -0500 Subject: [PATCH 238/334] Remove `dbg!()`s --- crates/client/src/telemetry.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 102362c422013773c44fa79d67ec6baa0bc8e3a7..8e8aa4942ca2f8a0ed950a14bcf5886ada14e6bd 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -469,8 +469,6 @@ impl Telemetry { let signed_in = state.metrics_id.is_some(); state.events_queue.push(EventWrapper { signed_in, event }); - dbg!(&state.events_queue.len()); - if state.installation_id.is_some() { if state.events_queue.len() >= MAX_QUEUE_LEN { drop(state); @@ -538,7 +536,6 @@ impl Telemetry { release_channel: state.release_channel, events, }; - dbg!("flush", &request_body); json_bytes.clear(); serde_json::to_writer(&mut json_bytes, &request_body)?; } From 54dcb1d33ca1dcf4a183c38cf3e0f33b87a63ce0 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Tue, 16 Jan 2024 18:34:52 -0500 Subject: [PATCH 239/334] Rename variable --- crates/client/src/telemetry.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 8e8aa4942ca2f8a0ed950a14bcf5886ada14e6bd..5ee039a8cb23c939351c4ff85283569fe4d0dd95 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -143,10 +143,10 @@ const MAX_QUEUE_LEN: usize = 5; const MAX_QUEUE_LEN: usize = 50; #[cfg(debug_assertions)] -const FLUSH_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); +const FLUSH_INTERVAL: Duration = Duration::from_secs(1); #[cfg(not(debug_assertions))] -const FLUSH_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5); +const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5); impl Telemetry { pub fn new(client: Arc, cx: &mut AppContext) -> Arc { @@ -461,7 +461,7 @@ impl Telemetry { let this = self.clone(); let executor = self.executor.clone(); state.flush_events_task = Some(self.executor.spawn(async move { - executor.timer(FLUSH_DEBOUNCE_INTERVAL).await; + executor.timer(FLUSH_INTERVAL).await; this.flush_events(); })); } From 26a3f68080f16dddba8ff9e480c065a41f1452f1 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 16 Jan 2024 18:49:37 -0500 Subject: [PATCH 240/334] Tweak mute indicator positioning (#4080) This PR tweaks the positioning of the mute indicators so that they cover a little bit less of the avatar: #### Before Screenshot 2024-01-16 at 6 32 51 PM #### After Screenshot 2024-01-16 at 6 26 48 PM (It's a bit hard to tell in the screenshot, but there is a gap between the bottom of the indicator and the top of the color ribbon). Release Notes: - N/A --- .../ui/src/components/avatar/avatar_audio_status_indicator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ui/src/components/avatar/avatar_audio_status_indicator.rs b/crates/ui/src/components/avatar/avatar_audio_status_indicator.rs index 55f98d3db3b8a49f827ffac0d13d69c00dfc5d75..943a8d4826110a020d3c35ba562651315ee53c3a 100644 --- a/crates/ui/src/components/avatar/avatar_audio_status_indicator.rs +++ b/crates/ui/src/components/avatar/avatar_audio_status_indicator.rs @@ -37,8 +37,8 @@ impl RenderOnce for AvatarAudioStatusIndicator { div() .absolute() - .bottom(rems(-1. / 16.)) - .right(rems(-4. / 16.)) + .bottom(rems(-3. / 16.)) + .right(rems(-6. / 16.)) .w(width_in_px + padding_x) .h(icon_size.rems()) .child( From db433586aacd65780826325125aea895b1ed34ee Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 16:52:55 -0800 Subject: [PATCH 241/334] Add some small test code for tracking down this list bug --- crates/gpui/src/app/test_context.rs | 12 +- crates/gpui/src/element.rs | 6 + crates/gpui/src/elements/list.rs | 227 ++++++++++++++++++---------- crates/gpui/src/interactive.rs | 7 +- crates/gpui/src/window.rs | 1 + 5 files changed, 163 insertions(+), 90 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 17c2a573a89e84573fc4667ae40964000c2ac3b7..3b01096bb5ad76ab250dc8df9e8fcb42dc6ff3e2 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -3,9 +3,9 @@ use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, - IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, Size, Task, - TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, - WindowContext, WindowHandle, WindowOptions, + InputEvent, IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, + Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, + VisualContext, WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -609,6 +609,12 @@ impl<'a> VisualTestContext { self.cx.simulate_input(self.window, input) } + /// Simulate an event from the platform, e.g. a SrollWheelEvent + pub fn simulate_event(&mut self, event: InputEvent) { + self.update(|cx| cx.dispatch_event(event)); + self.background_executor.run_until_parked(); + } + /// Simulates the user blurring the window. pub fn deactivate_window(&mut self) { if Some(self.window) == self.test_platform.active_window() { diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 179c2cb1e25db449556e92cfbf9710278dbd2b73..3022f9f30a5fa48d3a3d9b14b06011bdde2cc610 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -115,6 +115,12 @@ pub trait Render: 'static + Sized { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement; } +impl Render for () { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + () + } +} + /// You can derive [`IntoElement`] on any type that implements this trait. /// It is used to allow views to be expressed in terms of abstract data. pub trait RenderOnce: 'static { diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 2c076c8bdcdcb77fcc477f82dfba4f04a29bc2f2..7713bd27e1af3f903a512fd9995263b8dda308f3 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -1,6 +1,6 @@ use crate::{ point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, - DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, + DispatchPhase, Element, IntoElement, IsZero, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext, }; use collections::VecDeque; @@ -28,6 +28,7 @@ struct StateInner { render_item: Box AnyElement>, items: SumTree, logical_scroll_top: Option, + pending_scroll_delta: Pixels, alignment: ListAlignment, overdraw: Pixels, #[allow(clippy::type_complexity)] @@ -92,6 +93,7 @@ impl ListState { alignment: orientation, overdraw, scroll_handler: None, + pending_scroll_delta: px(0.), }))) } @@ -230,6 +232,8 @@ impl StateInner { delta: Point, cx: &mut WindowContext, ) { + // self.pending_scroll_delta += delta.y; + let scroll_max = (self.items.summary().height - height).max(px(0.)); let new_scroll_top = (self.scroll_top(scroll_top) - delta.y) .max(px(0.)) @@ -346,105 +350,119 @@ impl Element for List { height: AvailableSpace::MinContent, }; - // Render items after the scroll top, including those in the trailing overdraw let mut cursor = old_items.cursor::(); - cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); - for (ix, item) in cursor.by_ref().enumerate() { - let visible_height = rendered_height - scroll_top.offset_in_item; - if visible_height >= bounds.size.height + state.overdraw { - break; - } - // Use the previously cached height if available - let mut height = if let ListItem::Rendered { height } = item { - Some(*height) - } else { - None - }; - - // If we're within the visible area or the height wasn't cached, render and measure the item's element - if visible_height < bounds.size.height || height.is_none() { - let mut element = (state.render_item)(scroll_top.item_ix + ix, cx); - let element_size = element.measure(available_item_space, cx); - height = Some(element_size.height); - if visible_height < bounds.size.height { - item_elements.push_back(element); + loop { + // Render items after the scroll top, including those in the trailing overdraw + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + for (ix, item) in cursor.by_ref().enumerate() { + let visible_height = rendered_height - scroll_top.offset_in_item; + if visible_height >= bounds.size.height + state.overdraw { + break; + } + + // Use the previously cached height if available + let mut height = if let ListItem::Rendered { height } = item { + Some(*height) + } else { + None + }; + + // If we're within the visible area or the height wasn't cached, render and measure the item's element + if visible_height < bounds.size.height || height.is_none() { + let mut element = (state.render_item)(scroll_top.item_ix + ix, cx); + let element_size = element.measure(available_item_space, cx); + height = Some(element_size.height); + if visible_height < bounds.size.height { + item_elements.push_back(element); + } } + + let height = height.unwrap(); + rendered_height += height; + measured_items.push_back(ListItem::Rendered { height }); } - let height = height.unwrap(); - rendered_height += height; - measured_items.push_back(ListItem::Rendered { height }); - } + // Prepare to start walking upward from the item at the scroll top. + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + + // If the rendered items do not fill the visible region, then adjust + // the scroll top upward. + if rendered_height - scroll_top.offset_in_item < bounds.size.height { + while rendered_height < bounds.size.height { + cursor.prev(&()); + if cursor.item().is_some() { + let mut element = (state.render_item)(cursor.start().0, cx); + let element_size = element.measure(available_item_space, cx); + + rendered_height += element_size.height; + measured_items.push_front(ListItem::Rendered { + height: element_size.height, + }); + item_elements.push_front(element) + } else { + break; + } + } - // Prepare to start walking upward from the item at the scroll top. - cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + scroll_top = ListOffset { + item_ix: cursor.start().0, + offset_in_item: rendered_height - bounds.size.height, + }; + + match state.alignment { + ListAlignment::Top => { + scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.)); + state.logical_scroll_top = Some(scroll_top); + } + ListAlignment::Bottom => { + scroll_top = ListOffset { + item_ix: cursor.start().0, + offset_in_item: rendered_height - bounds.size.height, + }; + state.logical_scroll_top = None; + } + }; + } - // If the rendered items do not fill the visible region, then adjust - // the scroll top upward. - if rendered_height - scroll_top.offset_in_item < bounds.size.height { - while rendered_height < bounds.size.height { + // Measure items in the leading overdraw + let mut leading_overdraw = scroll_top.offset_in_item; + while leading_overdraw < state.overdraw { cursor.prev(&()); - if cursor.item().is_some() { - let mut element = (state.render_item)(cursor.start().0, cx); - let element_size = element.measure(available_item_space, cx); + if let Some(item) = cursor.item() { + let height = if let ListItem::Rendered { height } = item { + *height + } else { + let mut element = (state.render_item)(cursor.start().0, cx); + element.measure(available_item_space, cx).height + }; - rendered_height += element_size.height; - measured_items.push_front(ListItem::Rendered { - height: element_size.height, - }); - item_elements.push_front(element) + leading_overdraw += height; + measured_items.push_front(ListItem::Rendered { height }); } else { break; } } - scroll_top = ListOffset { - item_ix: cursor.start().0, - offset_in_item: rendered_height - bounds.size.height, - }; + let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); + let mut cursor = old_items.cursor::(); + let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); + new_items.extend(measured_items, &()); + cursor.seek(&Count(measured_range.end), Bias::Right, &()); + new_items.append(cursor.suffix(&()), &()); - match state.alignment { - ListAlignment::Top => { - scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.)); - state.logical_scroll_top = Some(scroll_top); - } - ListAlignment::Bottom => { - scroll_top = ListOffset { - item_ix: cursor.start().0, - offset_in_item: rendered_height - bounds.size.height, - }; - state.logical_scroll_top = None; - } - }; - } + state.items = new_items; + state.last_layout_bounds = Some(bounds); - // Measure items in the leading overdraw - let mut leading_overdraw = scroll_top.offset_in_item; - while leading_overdraw < state.overdraw { - cursor.prev(&()); - if let Some(item) = cursor.item() { - let height = if let ListItem::Rendered { height } = item { - *height - } else { - let mut element = (state.render_item)(cursor.start().0, cx); - element.measure(available_item_space, cx).height - }; + // if !state.pending_scroll_delta.is_zero() { + // // Do scroll manipulation - leading_overdraw += height; - measured_items.push_front(ListItem::Rendered { height }); - } else { - break; - } + // state.pending_scroll_delta = px(0.); + // } else { + break; + // } } - let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); - let mut cursor = old_items.cursor::(); - let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); - new_items.extend(measured_items, &()); - cursor.seek(&Count(measured_range.end), Bias::Right, &()); - new_items.append(cursor.suffix(&()), &()); - // Paint the visible items cx.with_content_mask(Some(ContentMask { bounds }), |cx| { let mut item_origin = bounds.origin; @@ -456,12 +474,12 @@ impl Element for List { } }); - state.items = new_items; - state.last_layout_bounds = Some(bounds); - let list_state = self.state.clone(); let height = bounds.size.height; + dbg!("scroll is being bound"); + cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { + dbg!("scroll dispatched!"); if phase == DispatchPhase::Bubble && bounds.contains(&event.position) && cx.was_top_layer(&event.position, cx.stacking_order()) @@ -562,3 +580,44 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height { self.0.partial_cmp(&other.height).unwrap() } } + +#[cfg(test)] +mod test { + + use crate::{self as gpui, Entity, TestAppContext}; + + #[gpui::test] + fn test_reset_after_paint_before_scroll(cx: &mut TestAppContext) { + use crate::{div, list, point, px, size, Element, ListState, Styled}; + + let (v, cx) = cx.add_window_view(|_| ()); + + let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _| { + div().h(px(10.)).w_full().into_any() + }); + + cx.update(|cx| { + cx.with_view_id(v.entity_id(), |cx| { + list(state.clone()) + .w_full() + .h_full() + .z_index(10) + .into_any() + .draw(point(px(0.0), px(0.0)), size(px(100.), px(20.)).into(), cx) + }); + }); + + state.reset(5); + + cx.simulate_event(gpui::InputEvent::ScrollWheel(gpui::ScrollWheelEvent { + position: point(px(1.), px(1.)), + delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-500.))), + ..Default::default() + })); + + assert_eq!(state.logical_scroll_top().item_ix, 0); + assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); + + panic!("We should not get here yet!") + } +} diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index dfccfc35307f1eb2e75e2d7e8fe8eb73b2c4b7ef..419be9ac3860e3c33c0c27d58754054000d24a15 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -2,7 +2,7 @@ use crate::{ div, point, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; -use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; +use std::{any::Any, default, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct KeyDownEvent { @@ -30,9 +30,10 @@ impl Deref for ModifiersChangedEvent { /// The phase of a touch motion event. /// Based on the winit enum of the same name. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub enum TouchPhase { Started, + #[default] Moved, Ended, } @@ -136,7 +137,7 @@ impl MouseMoveEvent { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct ScrollWheelEvent { pub position: Point, pub delta: ScrollDelta, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 869d6b18268cc64bc18f9187dd34e8032237d28b..df787603471ca1920623dbee8e3d8e0e6d3a3b86 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1716,6 +1716,7 @@ impl<'a> WindowContext<'a> { .mouse_listeners .remove(&event.type_id()) { + dbg!(handlers.len()); // Because handlers may add other handlers, we sort every time. handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); From 8be798d1c0980c8a614226ad2846802bb727b1c6 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 16 Jan 2024 19:47:59 -0700 Subject: [PATCH 242/334] Limit number of collaborators in local Facepiles --- crates/collab_ui/src/collab_titlebar_item.rs | 44 ++++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 077e08fac78960449824ddb3473e74f916b7577c..6bbb02c703c4327146b66c4b3aaa3815a1d8611e 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -3,7 +3,7 @@ use auto_update::AutoUpdateStatus; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Client, User, UserStore}; use gpui::{ - actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Element, Hsla, + actions, canvas, div, point, px, Action, AnyElement, AppContext, Element, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render, StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowBounds, @@ -480,7 +480,9 @@ impl CollabTitlebarItem { return None; } + const FACEPILE_LIMIT: usize = 3; let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id)); + let extra_count = followers.len().saturating_sub(FACEPILE_LIMIT); let pile = FacePile::default() .child( @@ -502,18 +504,34 @@ impl CollabTitlebarItem { ) }), ) - .children(followers.iter().filter_map(|follower_peer_id| { - let follower = room - .remote_participants() - .values() - .find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user)) - .or_else(|| { - (self.client.peer_id() == Some(*follower_peer_id)).then_some(current_user) - })? - .clone(); - - Some(Avatar::new(follower.avatar_uri.clone())) - })); + .children( + followers + .iter() + .take(FACEPILE_LIMIT) + .filter_map(|follower_peer_id| { + let follower = room + .remote_participants() + .values() + .find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user)) + .or_else(|| { + (self.client.peer_id() == Some(*follower_peer_id)) + .then_some(current_user) + })? + .clone(); + + Some(Avatar::new(follower.avatar_uri.clone())) + }), + ) + .children(if extra_count > 0 { + Some( + div() + .ml_1() + .child(Label::new(format!("+{extra_count}"))) + .into_any_element(), + ) + } else { + None + }); Some(pile) } From 1d5b237b642ed4049eb2066eebd3acd2acf7e6b4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 16 Jan 2024 19:35:26 -0700 Subject: [PATCH 243/334] Allow leaving calls once project is unshared --- crates/collab_ui/src/collab_titlebar_item.rs | 9 +++------ crates/workspace/src/workspace.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 077e08fac78960449824ddb3473e74f916b7577c..6c0718f41e62e89116dbe4129e48f4b93d1e6ea9 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -3,7 +3,7 @@ use auto_update::AutoUpdateStatus; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Client, User, UserStore}; use gpui::{ - actions, canvas, div, point, px, rems, Action, AnyElement, AppContext, Element, Hsla, + actions, canvas, div, point, px, Action, AnyElement, AppContext, Element, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render, StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowBounds, @@ -19,7 +19,7 @@ use ui::{ }; use util::ResultExt; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; -use workspace::{notifications::NotifyResultExt, Workspace}; +use workspace::{notifications::NotifyResultExt, titlebar_height, Workspace}; const MAX_PROJECT_NAME_LENGTH: usize = 40; const MAX_BRANCH_NAME_LENGTH: usize = 40; @@ -62,10 +62,7 @@ impl Render for CollabTitlebarItem { .id("titlebar") .justify_between() .w_full() - .h(rems(1.75)) - // Set a non-scaling min-height here to ensure the titlebar is - // always at least the height of the traffic lights. - .min_h(px(32.)) + .h(titlebar_height(cx)) .map(|this| { if matches!(cx.window_bounds(), WindowBounds::Fullscreen) { this.pl_2() diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f2949f58e5d84233d4d2fc36ba8fc1141cacab0c..1d06db5de3b459b289d3b0392c3fb91ac594a760 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -25,7 +25,7 @@ use futures::{ Future, FutureExt, StreamExt, }; use gpui::{ - actions, canvas, div, impl_actions, point, size, Action, AnyElement, AnyModel, AnyView, + actions, canvas, div, impl_actions, point, px, size, Action, AnyElement, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow, Bounds, Context, Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId, ManagedView, Model, @@ -4302,6 +4302,10 @@ fn parse_pixel_size_env_var(value: &str) -> Option> { Some(size((width as f64).into(), (height as f64).into())) } +pub fn titlebar_height(cx: &mut WindowContext) -> Pixels { + (1.75 * cx.rem_size()).max(px(32.)) +} + struct DisconnectedOverlay; impl Element for DisconnectedOverlay { @@ -4318,7 +4322,7 @@ impl Element for DisconnectedOverlay { .bg(background) .absolute() .left_0() - .top_0() + .top(titlebar_height(cx)) .size_full() .flex() .items_center() From cae35d3334adea86d560953cd02388a10bb9b313 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 22:19:55 -0800 Subject: [PATCH 244/334] Fix draw helper, add helper traits for selecting groupings of input events --- crates/gpui/src/app/test_context.rs | 56 ++++--- crates/gpui/src/elements/list.rs | 29 ++-- crates/gpui/src/interactive.rs | 170 ++++++++++++++-------- crates/gpui/src/platform.rs | 6 +- crates/gpui/src/platform/mac/events.rs | 8 +- crates/gpui/src/platform/mac/platform.rs | 10 +- crates/gpui/src/platform/mac/window.rs | 56 +++---- crates/gpui/src/platform/test/platform.rs | 2 +- crates/gpui/src/platform/test/window.rs | 14 +- crates/gpui/src/window.rs | 61 ++++---- 10 files changed, 243 insertions(+), 169 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 3b01096bb5ad76ab250dc8df9e8fcb42dc6ff3e2..556c104f69b9da1a20b32cb0fa325f5aeb975ce9 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -1,11 +1,11 @@ #![deny(missing_docs)] use crate::{ - div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, - BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, - InputEvent, IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, - Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, - VisualContext, WindowContext, WindowHandle, WindowOptions, + Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, + AvailableSpace, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, + ForegroundExecutor, InputEvent, Keystroke, Model, ModelContext, Pixels, Platform, Point, + Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, + ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -167,10 +167,14 @@ impl TestAppContext { } /// Adds a new window with no content. - pub fn add_empty_window(&mut self) -> AnyWindowHandle { + pub fn add_empty_window(&mut self) -> &mut VisualTestContext { let mut cx = self.app.borrow_mut(); - cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) - .any_handle + let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| ())); + drop(cx); + let cx = Box::new(VisualTestContext::from_window(*window.deref(), self)); + cx.run_until_parked(); + // it might be nice to try and cleanup these at the end of each test. + Box::leak(cx) } /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used @@ -609,9 +613,32 @@ impl<'a> VisualTestContext { self.cx.simulate_input(self.window, input) } + /// Draw an element to the window. Useful for simulating events or actions + pub fn draw( + &mut self, + origin: Point, + space: Size, + f: impl FnOnce(&mut WindowContext) -> AnyElement, + ) { + self.update(|cx| { + let entity_id = cx + .window + .root_view + .as_ref() + .expect("Can't draw to this window without a root view") + .entity_id(); + cx.with_view_id(entity_id, |cx| { + f(cx).draw(origin, space, cx); + }); + + cx.refresh(); + }) + } + /// Simulate an event from the platform, e.g. a SrollWheelEvent - pub fn simulate_event(&mut self, event: InputEvent) { - self.update(|cx| cx.dispatch_event(event)); + /// Make sure you've called [VisualTestContext::draw] first! + pub fn simulate_event(&mut self, event: E) { + self.update(|cx| cx.dispatch_event(event.to_platform_input())); self.background_executor.run_until_parked(); } @@ -769,12 +796,3 @@ impl AnyWindowHandle { self.update(cx, |_, cx| cx.new_view(build_view)).unwrap() } } - -/// An EmptyView for testing. -pub struct EmptyView {} - -impl Render for EmptyView { - fn render(&mut self, _cx: &mut crate::ViewContext) -> impl IntoElement { - div() - } -} diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 7713bd27e1af3f903a512fd9995263b8dda308f3..96067d24000033e5c3b292eea3d70871887be7cc 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -1,6 +1,6 @@ use crate::{ point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, - DispatchPhase, Element, IntoElement, IsZero, Pixels, Point, ScrollWheelEvent, Size, Style, + DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext, }; use collections::VecDeque; @@ -584,36 +584,33 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height { #[cfg(test)] mod test { - use crate::{self as gpui, Entity, TestAppContext}; + use gpui::{ScrollDelta, ScrollWheelEvent}; + + use crate::{self as gpui, TestAppContext}; #[gpui::test] fn test_reset_after_paint_before_scroll(cx: &mut TestAppContext) { use crate::{div, list, point, px, size, Element, ListState, Styled}; - let (v, cx) = cx.add_window_view(|_| ()); + let cx = cx.add_empty_window(); let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _| { div().h(px(10.)).w_full().into_any() }); - cx.update(|cx| { - cx.with_view_id(v.entity_id(), |cx| { - list(state.clone()) - .w_full() - .h_full() - .z_index(10) - .into_any() - .draw(point(px(0.0), px(0.0)), size(px(100.), px(20.)).into(), cx) - }); - }); + cx.draw( + point(px(0.), px(0.)), + size(px(100.), px(20.)).into(), + |_| list(state.clone()).w_full().h_full().z_index(10).into_any(), + ); state.reset(5); - cx.simulate_event(gpui::InputEvent::ScrollWheel(gpui::ScrollWheelEvent { + cx.simulate_event(ScrollWheelEvent { position: point(px(1.), px(1.)), - delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-500.))), + delta: ScrollDelta::Pixels(point(px(0.), px(-500.))), ..Default::default() - })); + }); assert_eq!(state.logical_scroll_top().item_ix, 0); assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 419be9ac3860e3c33c0c27d58754054000d24a15..86e0a6378e29c290aa7e83ab712f2455997d1d4f 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -1,8 +1,14 @@ use crate::{ - div, point, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, + point, seal::Sealed, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; -use std::{any::Any, default, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; +use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf}; + +pub trait InputEvent: Sealed + 'static { + fn to_platform_input(self) -> PlatformInput; +} +pub trait KeyEvent: InputEvent {} +pub trait MouseEvent: InputEvent {} #[derive(Clone, Debug, Eq, PartialEq)] pub struct KeyDownEvent { @@ -10,16 +16,40 @@ pub struct KeyDownEvent { pub is_held: bool, } +impl Sealed for KeyDownEvent {} +impl InputEvent for KeyDownEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::KeyDown(self) + } +} +impl KeyEvent for KeyDownEvent {} + #[derive(Clone, Debug)] pub struct KeyUpEvent { pub keystroke: Keystroke, } +impl Sealed for KeyUpEvent {} +impl InputEvent for KeyUpEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::KeyUp(self) + } +} +impl KeyEvent for KeyUpEvent {} + #[derive(Clone, Debug, Default)] pub struct ModifiersChangedEvent { pub modifiers: Modifiers, } +impl Sealed for ModifiersChangedEvent {} +impl InputEvent for ModifiersChangedEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::ModifiersChanged(self) + } +} +impl KeyEvent for ModifiersChangedEvent {} + impl Deref for ModifiersChangedEvent { type Target = Modifiers; @@ -46,6 +76,14 @@ pub struct MouseDownEvent { pub click_count: usize, } +impl Sealed for MouseDownEvent {} +impl InputEvent for MouseDownEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::MouseDown(self) + } +} +impl MouseEvent for MouseDownEvent {} + #[derive(Clone, Debug, Default)] pub struct MouseUpEvent { pub button: MouseButton, @@ -54,38 +92,20 @@ pub struct MouseUpEvent { pub click_count: usize, } +impl Sealed for MouseUpEvent {} +impl InputEvent for MouseUpEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::MouseUp(self) + } +} +impl MouseEvent for MouseUpEvent {} + #[derive(Clone, Debug, Default)] pub struct ClickEvent { pub down: MouseDownEvent, pub up: MouseUpEvent, } -pub struct Drag -where - R: Fn(&mut V, &mut ViewContext) -> E, - V: 'static, - E: IntoElement, -{ - pub state: S, - pub render_drag_handle: R, - view_element_types: PhantomData<(V, E)>, -} - -impl Drag -where - R: Fn(&mut V, &mut ViewContext) -> E, - V: 'static, - E: Element, -{ - pub fn new(state: S, render_drag_handle: R) -> Self { - Drag { - state, - render_drag_handle, - view_element_types: Default::default(), - } - } -} - #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] pub enum MouseButton { Left, @@ -131,6 +151,14 @@ pub struct MouseMoveEvent { pub modifiers: Modifiers, } +impl Sealed for MouseMoveEvent {} +impl InputEvent for MouseMoveEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::MouseMove(self) + } +} +impl MouseEvent for MouseMoveEvent {} + impl MouseMoveEvent { pub fn dragging(&self) -> bool { self.pressed_button == Some(MouseButton::Left) @@ -145,6 +173,14 @@ pub struct ScrollWheelEvent { pub touch_phase: TouchPhase, } +impl Sealed for ScrollWheelEvent {} +impl InputEvent for ScrollWheelEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::ScrollWheel(self) + } +} +impl MouseEvent for ScrollWheelEvent {} + impl Deref for ScrollWheelEvent { type Target = Modifiers; @@ -202,6 +238,14 @@ pub struct MouseExitEvent { pub modifiers: Modifiers, } +impl Sealed for MouseExitEvent {} +impl InputEvent for MouseExitEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::MouseExited(self) + } +} +impl MouseEvent for MouseExitEvent {} + impl Deref for MouseExitEvent { type Target = Modifiers; @@ -221,7 +265,7 @@ impl ExternalPaths { impl Render for ExternalPaths { fn render(&mut self, _: &mut ViewContext) -> impl IntoElement { - div() // Intentionally left empty because the platform will render icons for the dragged files + () // Intentionally left empty because the platform will render icons for the dragged files } } @@ -240,8 +284,16 @@ pub enum FileDropEvent { Exited, } +impl Sealed for FileDropEvent {} +impl InputEvent for FileDropEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::FileDrop(self) + } +} +impl MouseEvent for FileDropEvent {} + #[derive(Clone, Debug)] -pub enum InputEvent { +pub enum PlatformInput { KeyDown(KeyDownEvent), KeyUp(KeyUpEvent), ModifiersChanged(ModifiersChangedEvent), @@ -253,19 +305,19 @@ pub enum InputEvent { FileDrop(FileDropEvent), } -impl InputEvent { +impl PlatformInput { pub fn position(&self) -> Option> { match self { - InputEvent::KeyDown { .. } => None, - InputEvent::KeyUp { .. } => None, - InputEvent::ModifiersChanged { .. } => None, - InputEvent::MouseDown(event) => Some(event.position), - InputEvent::MouseUp(event) => Some(event.position), - InputEvent::MouseMove(event) => Some(event.position), - InputEvent::MouseExited(event) => Some(event.position), - InputEvent::ScrollWheel(event) => Some(event.position), - InputEvent::FileDrop(FileDropEvent::Exited) => None, - InputEvent::FileDrop( + PlatformInput::KeyDown { .. } => None, + PlatformInput::KeyUp { .. } => None, + PlatformInput::ModifiersChanged { .. } => None, + PlatformInput::MouseDown(event) => Some(event.position), + PlatformInput::MouseUp(event) => Some(event.position), + PlatformInput::MouseMove(event) => Some(event.position), + PlatformInput::MouseExited(event) => Some(event.position), + PlatformInput::ScrollWheel(event) => Some(event.position), + PlatformInput::FileDrop(FileDropEvent::Exited) => None, + PlatformInput::FileDrop( FileDropEvent::Entered { position, .. } | FileDropEvent::Pending { position, .. } | FileDropEvent::Submit { position, .. }, @@ -275,29 +327,29 @@ impl InputEvent { pub fn mouse_event(&self) -> Option<&dyn Any> { match self { - InputEvent::KeyDown { .. } => None, - InputEvent::KeyUp { .. } => None, - InputEvent::ModifiersChanged { .. } => None, - InputEvent::MouseDown(event) => Some(event), - InputEvent::MouseUp(event) => Some(event), - InputEvent::MouseMove(event) => Some(event), - InputEvent::MouseExited(event) => Some(event), - InputEvent::ScrollWheel(event) => Some(event), - InputEvent::FileDrop(event) => Some(event), + PlatformInput::KeyDown { .. } => None, + PlatformInput::KeyUp { .. } => None, + PlatformInput::ModifiersChanged { .. } => None, + PlatformInput::MouseDown(event) => Some(event), + PlatformInput::MouseUp(event) => Some(event), + PlatformInput::MouseMove(event) => Some(event), + PlatformInput::MouseExited(event) => Some(event), + PlatformInput::ScrollWheel(event) => Some(event), + PlatformInput::FileDrop(event) => Some(event), } } pub fn keyboard_event(&self) -> Option<&dyn Any> { match self { - InputEvent::KeyDown(event) => Some(event), - InputEvent::KeyUp(event) => Some(event), - InputEvent::ModifiersChanged(event) => Some(event), - InputEvent::MouseDown(_) => None, - InputEvent::MouseUp(_) => None, - InputEvent::MouseMove(_) => None, - InputEvent::MouseExited(_) => None, - InputEvent::ScrollWheel(_) => None, - InputEvent::FileDrop(_) => None, + PlatformInput::KeyDown(event) => Some(event), + PlatformInput::KeyUp(event) => Some(event), + PlatformInput::ModifiersChanged(event) => Some(event), + PlatformInput::MouseDown(_) => None, + PlatformInput::MouseUp(_) => None, + PlatformInput::MouseMove(_) => None, + PlatformInput::MouseExited(_) => None, + PlatformInput::ScrollWheel(_) => None, + PlatformInput::FileDrop(_) => None, } } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 5a2335919ebe6ff9b2277e02d4f6f55b4dc9a80c..8a2e3ea272d392f4db1ba777523ff70b225c1539 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -7,7 +7,7 @@ mod test; use crate::{ Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, - FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap, LineLayout, Pixels, + FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, TaskLabel, }; @@ -88,7 +88,7 @@ pub(crate) trait Platform: 'static { fn on_resign_active(&self, callback: Box); fn on_quit(&self, callback: Box); fn on_reopen(&self, callback: Box); - fn on_event(&self, callback: Box bool>); + fn on_event(&self, callback: Box bool>); fn set_menus(&self, menus: Vec, keymap: &Keymap); fn on_app_menu_action(&self, callback: Box); @@ -155,7 +155,7 @@ pub trait PlatformWindow { fn zoom(&self); fn toggle_full_screen(&self); fn on_request_frame(&self, callback: Box); - fn on_input(&self, callback: Box bool>); + fn on_input(&self, callback: Box bool>); fn on_active_status_change(&self, callback: Box); fn on_resize(&self, callback: Box, f32)>); fn on_fullscreen(&self, callback: Box); diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index c67018ad5d4666ec8ae15511e4739f811ebcf309..f84833d3cbb1678c1ee1ee9b0c1793bb9df8bae0 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -1,7 +1,7 @@ use crate::{ - point, px, InputEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, - MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, - Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase, + point, px, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, + MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, + PlatformInput, ScrollDelta, ScrollWheelEvent, TouchPhase, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType}, @@ -82,7 +82,7 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers { } } -impl InputEvent { +impl PlatformInput { pub unsafe fn from_native(native_event: id, window_height: Option) -> Option { let event_type = native_event.eventType(); diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 8061cc136064c8624d4e8ad217d0a1703274001f..499ac0b59104d9ab0b8204a95c01371a228a6b47 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1,8 +1,8 @@ use super::{events::key_to_native, BoolExt}; use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, - ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, - MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, + ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, + MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; @@ -153,7 +153,7 @@ pub struct MacPlatformState { resign_active: Option>, reopen: Option>, quit: Option>, - event: Option bool>>, + event: Option bool>>, menu_command: Option>, validate_menu_command: Option bool>>, will_open_menu: Option>, @@ -637,7 +637,7 @@ impl Platform for MacPlatform { self.0.lock().reopen = Some(callback); } - fn on_event(&self, callback: Box bool>) { + fn on_event(&self, callback: Box bool>) { self.0.lock().event = Some(callback); } @@ -976,7 +976,7 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform { extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { unsafe { - if let Some(event) = InputEvent::from_native(native_event, None) { + if let Some(event) = PlatformInput::from_native(native_event, None) { let platform = get_mac_platform(this); let mut lock = platform.0.lock(); if let Some(mut callback) = lock.event.take() { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index c364021281a3690635713bd756c520b1d5f3558b..134390bb79900b0cc09efba720b373303d8cd26b 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1,9 +1,9 @@ use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange}; use crate::{ display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths, - FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, - Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent, Keystroke, Modifiers, + ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, + PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, }; use block::ConcreteBlock; @@ -319,7 +319,7 @@ struct MacWindowState { renderer: MetalRenderer, kind: WindowKind, request_frame_callback: Option>, - event_callback: Option bool>>, + event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option, f32)>>, fullscreen_callback: Option>, @@ -333,7 +333,7 @@ struct MacWindowState { synthetic_drag_counter: usize, last_fresh_keydown: Option, traffic_light_position: Option>, - previous_modifiers_changed_event: Option, + previous_modifiers_changed_event: Option, // State tracking what the IME did after the last request ime_state: ImeState, // Retains the last IME Text @@ -928,7 +928,7 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().request_frame_callback = Some(callback); } - fn on_input(&self, callback: Box bool>) { + fn on_input(&self, callback: Box bool>) { self.0.as_ref().lock().event_callback = Some(callback); } @@ -1053,9 +1053,9 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut lock = window_state.as_ref().lock(); let window_height = lock.content_size().height; - let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) }; + let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) }; - if let Some(InputEvent::KeyDown(event)) = event { + if let Some(PlatformInput::KeyDown(event)) = event { // For certain keystrokes, macOS will first dispatch a "key equivalent" event. // If that event isn't handled, it will then dispatch a "key down" event. GPUI // makes no distinction between these two types of events, so we need to ignore @@ -1102,7 +1102,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: .flatten() .is_some(); if !is_composing { - handled = callback(InputEvent::KeyDown(event)); + handled = callback(PlatformInput::KeyDown(event)); } if !handled { @@ -1146,11 +1146,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let is_active = unsafe { lock.native_window.isKeyWindow() == YES }; let window_height = lock.content_size().height; - let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) }; + let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) }; if let Some(mut event) = event { match &mut event { - InputEvent::MouseDown( + PlatformInput::MouseDown( event @ MouseDownEvent { button: MouseButton::Left, modifiers: Modifiers { control: true, .. }, @@ -1172,7 +1172,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { // Because we map a ctrl-left_down to a right_down -> right_up let's ignore // the ctrl-left_up to avoid having a mismatch in button down/up events if the // user is still holding ctrl when releasing the left mouse button - InputEvent::MouseUp( + PlatformInput::MouseUp( event @ MouseUpEvent { button: MouseButton::Left, modifiers: Modifiers { control: true, .. }, @@ -1194,7 +1194,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { }; match &event { - InputEvent::MouseMove( + PlatformInput::MouseMove( event @ MouseMoveEvent { pressed_button: Some(_), .. @@ -1216,15 +1216,15 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { } } - InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, + PlatformInput::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, - InputEvent::MouseUp(MouseUpEvent { .. }) => { + PlatformInput::MouseUp(MouseUpEvent { .. }) => { lock.synthetic_drag_counter += 1; } - InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { + PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { // Only raise modifiers changed event when they have actually changed - if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent { + if let Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers: prev_modifiers, })) = &lock.previous_modifiers_changed_event { @@ -1258,7 +1258,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { key: ".".into(), ime_key: None, }; - let event = InputEvent::KeyDown(KeyDownEvent { + let event = PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held: false, }); @@ -1655,7 +1655,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr if send_new_event(&window_state, { let position = drag_event_position(&window_state, dragging_info); let paths = external_paths_from_event(dragging_info); - InputEvent::FileDrop(FileDropEvent::Entered { position, paths }) + PlatformInput::FileDrop(FileDropEvent::Entered { position, paths }) }) { window_state.lock().external_files_dragged = true; NSDragOperationCopy @@ -1669,7 +1669,7 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr let position = drag_event_position(&window_state, dragging_info); if send_new_event( &window_state, - InputEvent::FileDrop(FileDropEvent::Pending { position }), + PlatformInput::FileDrop(FileDropEvent::Pending { position }), ) { NSDragOperationCopy } else { @@ -1679,7 +1679,10 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; - send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited)); + send_new_event( + &window_state, + PlatformInput::FileDrop(FileDropEvent::Exited), + ); window_state.lock().external_files_dragged = false; } @@ -1688,7 +1691,7 @@ extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) - let position = drag_event_position(&window_state, dragging_info); if send_new_event( &window_state, - InputEvent::FileDrop(FileDropEvent::Submit { position }), + PlatformInput::FileDrop(FileDropEvent::Submit { position }), ) { YES } else { @@ -1712,7 +1715,10 @@ fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths { extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; - send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited)); + send_new_event( + &window_state, + PlatformInput::FileDrop(FileDropEvent::Exited), + ); } async fn synthetic_drag( @@ -1727,7 +1733,7 @@ async fn synthetic_drag( if lock.synthetic_drag_counter == drag_id { if let Some(mut callback) = lock.event_callback.take() { drop(lock); - callback(InputEvent::MouseMove(event.clone())); + callback(PlatformInput::MouseMove(event.clone())); window_state.lock().event_callback = Some(callback); } } else { @@ -1737,7 +1743,7 @@ async fn synthetic_drag( } } -fn send_new_event(window_state_lock: &Mutex, e: InputEvent) -> bool { +fn send_new_event(window_state_lock: &Mutex, e: PlatformInput) -> bool { let window_state = window_state_lock.lock().event_callback.take(); if let Some(mut callback) = window_state { callback(e); diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 3a4f5bb36a1360d7d994e82e9347563f985caa79..f5e2170b28acdeae304495172c7b6bbac568bcf4 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -239,7 +239,7 @@ impl Platform for TestPlatform { unimplemented!() } - fn on_event(&self, _callback: Box bool>) { + fn on_event(&self, _callback: Box bool>) { unimplemented!() } diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index f05e13e3a027e2be9d4f17690fe7162a73396d03..5c8a3e5a59cf91b86d50c311c1beeccfd5ac2840 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -1,7 +1,7 @@ use crate::{ - px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent, - Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, - Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions, + px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, KeyDownEvent, Keystroke, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, + Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions, }; use collections::HashMap; use parking_lot::Mutex; @@ -19,7 +19,7 @@ pub struct TestWindowState { platform: Weak, sprite_atlas: Arc, pub(crate) should_close_handler: Option bool>>, - input_callback: Option bool>>, + input_callback: Option bool>>, active_status_change_callback: Option>, resize_callback: Option, f32)>>, moved_callback: Option>, @@ -85,7 +85,7 @@ impl TestWindow { self.0.lock().active_status_change_callback = Some(callback); } - pub fn simulate_input(&mut self, event: InputEvent) -> bool { + pub fn simulate_input(&mut self, event: PlatformInput) -> bool { let mut lock = self.0.lock(); let Some(mut callback) = lock.input_callback.take() else { return false; @@ -97,7 +97,7 @@ impl TestWindow { } pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) { - if self.simulate_input(InputEvent::KeyDown(KeyDownEvent { + if self.simulate_input(PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held, })) { @@ -220,7 +220,7 @@ impl PlatformWindow for TestWindow { fn on_request_frame(&self, _callback: Box) {} - fn on_input(&self, callback: Box bool>) { + fn on_input(&self, callback: Box bool>) { self.0.lock().input_callback = Some(callback) } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index df787603471ca1920623dbee8e3d8e0e6d3a3b86..00b17ba3c07ba6db532035c37d5bf7cdd74e9f59 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -5,13 +5,14 @@ use crate::{ AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, - ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, - Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, - Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, - PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size, Style, SubscriberSet, - Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, - WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + ImageData, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, KeystrokeEvent, LayoutId, + Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, + MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, + PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, + RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, Shadow, + SharedString, Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, + Underline, UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, + SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::{FxHashMap, FxHashSet}; @@ -968,7 +969,7 @@ impl<'a> WindowContext<'a> { /// Register a mouse event listener on the window for the next frame. The type of event /// is determined by the first parameter of the given listener. When the next frame is rendered /// the listener will be cleared. - pub fn on_mouse_event( + pub fn on_mouse_event( &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { @@ -996,7 +997,7 @@ impl<'a> WindowContext<'a> { /// /// This is a fairly low-level method, so prefer using event handlers on elements unless you have /// a specific need to register a global listener. - pub fn on_key_event( + pub fn on_key_event( &mut self, listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { @@ -1617,7 +1618,7 @@ impl<'a> WindowContext<'a> { } /// Dispatch a mouse or keyboard event on the window. - pub fn dispatch_event(&mut self, event: InputEvent) -> bool { + pub fn dispatch_event(&mut self, event: PlatformInput) -> bool { // Handlers may set this to false by calling `stop_propagation`. self.app.propagate_event = true; // Handlers may set this to true by calling `prevent_default`. @@ -1626,37 +1627,37 @@ impl<'a> WindowContext<'a> { let event = match event { // Track the mouse position with our own state, since accessing the platform // API for the mouse position can only occur on the main thread. - InputEvent::MouseMove(mouse_move) => { + PlatformInput::MouseMove(mouse_move) => { self.window.mouse_position = mouse_move.position; self.window.modifiers = mouse_move.modifiers; - InputEvent::MouseMove(mouse_move) + PlatformInput::MouseMove(mouse_move) } - InputEvent::MouseDown(mouse_down) => { + PlatformInput::MouseDown(mouse_down) => { self.window.mouse_position = mouse_down.position; self.window.modifiers = mouse_down.modifiers; - InputEvent::MouseDown(mouse_down) + PlatformInput::MouseDown(mouse_down) } - InputEvent::MouseUp(mouse_up) => { + PlatformInput::MouseUp(mouse_up) => { self.window.mouse_position = mouse_up.position; self.window.modifiers = mouse_up.modifiers; - InputEvent::MouseUp(mouse_up) + PlatformInput::MouseUp(mouse_up) } - InputEvent::MouseExited(mouse_exited) => { + PlatformInput::MouseExited(mouse_exited) => { self.window.modifiers = mouse_exited.modifiers; - InputEvent::MouseExited(mouse_exited) + PlatformInput::MouseExited(mouse_exited) } - InputEvent::ModifiersChanged(modifiers_changed) => { + PlatformInput::ModifiersChanged(modifiers_changed) => { self.window.modifiers = modifiers_changed.modifiers; - InputEvent::ModifiersChanged(modifiers_changed) + PlatformInput::ModifiersChanged(modifiers_changed) } - InputEvent::ScrollWheel(scroll_wheel) => { + PlatformInput::ScrollWheel(scroll_wheel) => { self.window.mouse_position = scroll_wheel.position; self.window.modifiers = scroll_wheel.modifiers; - InputEvent::ScrollWheel(scroll_wheel) + PlatformInput::ScrollWheel(scroll_wheel) } // Translate dragging and dropping of external files from the operating system // to internal drag and drop events. - InputEvent::FileDrop(file_drop) => match file_drop { + PlatformInput::FileDrop(file_drop) => match file_drop { FileDropEvent::Entered { position, paths } => { self.window.mouse_position = position; if self.active_drag.is_none() { @@ -1666,7 +1667,7 @@ impl<'a> WindowContext<'a> { cursor_offset: position, }); } - InputEvent::MouseMove(MouseMoveEvent { + PlatformInput::MouseMove(MouseMoveEvent { position, pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), @@ -1674,7 +1675,7 @@ impl<'a> WindowContext<'a> { } FileDropEvent::Pending { position } => { self.window.mouse_position = position; - InputEvent::MouseMove(MouseMoveEvent { + PlatformInput::MouseMove(MouseMoveEvent { position, pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), @@ -1683,21 +1684,21 @@ impl<'a> WindowContext<'a> { FileDropEvent::Submit { position } => { self.activate(true); self.window.mouse_position = position; - InputEvent::MouseUp(MouseUpEvent { + PlatformInput::MouseUp(MouseUpEvent { button: MouseButton::Left, position, modifiers: Modifiers::default(), click_count: 1, }) } - FileDropEvent::Exited => InputEvent::MouseUp(MouseUpEvent { + FileDropEvent::Exited => PlatformInput::MouseUp(MouseUpEvent { button: MouseButton::Left, position: Point::default(), modifiers: Modifiers::default(), click_count: 1, }), }, - InputEvent::KeyDown(_) | InputEvent::KeyUp(_) => event, + PlatformInput::KeyDown(_) | PlatformInput::KeyUp(_) => event, }; if let Some(any_mouse_event) = event.mouse_event() { @@ -2976,7 +2977,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { /// Add a listener for any mouse event that occurs in the window. /// This is a fairly low level method. /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc. - pub fn on_mouse_event( + pub fn on_mouse_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, ) { @@ -2989,7 +2990,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } /// Register a callback to be invoked when the given Key Event is dispatched to the window. - pub fn on_key_event( + pub fn on_key_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, ) { From a99ee5e599dbb491982fc052a3f093e536444db0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 22:30:44 -0800 Subject: [PATCH 245/334] Fix test failures --- crates/collab/src/tests/editor_tests.rs | 78 ++++++++++--------------- crates/gpui/src/app/test_context.rs | 5 ++ crates/search/src/buffer_search.rs | 8 +-- 3 files changed, 40 insertions(+), 51 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 0c3601b07531bf5c77459fd5530a31ba8ef68717..a5fa187d24acc93af3b2ff64dbf2ef96fffa7ea3 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -185,31 +185,27 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - let window_a = cx_a.add_empty_window(); - let editor_a = - window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); + let cx_a = cx_a.add_empty_window(); + let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); let mut editor_cx_a = EditorTestContext { - cx: VisualTestContext::from_window(window_a, cx_a), - window: window_a.into(), + cx: cx_a.clone(), + window: cx_a.handle(), editor: editor_a, assertion_cx: AssertionContextManager::new(), }; - let window_b = cx_b.add_empty_window(); - let mut cx_b = VisualTestContext::from_window(window_b, cx_b); - + let cx_b = cx_b.add_empty_window(); // Open a buffer as client B let buffer_b = project_b - .update(&mut cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - let editor_b = window_b.build_view(&mut cx_b, |cx| { - Editor::for_buffer(buffer_b, Some(project_b), cx) - }); + let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), cx)); + let mut editor_cx_b = EditorTestContext { - cx: cx_b, - window: window_b.into(), + cx: cx_b.clone(), + window: cx_b.handle(), editor: editor_b, assertion_cx: AssertionContextManager::new(), }; @@ -311,10 +307,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let window_b = cx_b.add_empty_window(); - let editor_b = window_b.build_view(cx_b, |cx| { - Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx) - }); + let cx_b = cx_b.add_empty_window(); + let editor_b = + cx_b.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)); let fake_language_server = fake_language_servers.next().await.unwrap(); cx_a.background_executor.run_until_parked(); @@ -323,10 +318,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu assert!(!buffer.completion_triggers().is_empty()) }); - let mut cx_b = VisualTestContext::from_window(window_b, cx_b); - // Type a completion trigger character as the guest. - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(".", cx); }); @@ -392,8 +385,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu }); // Confirm a completion on the guest. - - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { assert!(editor.context_menu_visible()); editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx); assert_eq!(editor.text(cx), "fn main() { a.first_method() }"); @@ -431,7 +423,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu ); }); - buffer_b.read_with(&mut cx_b, |buffer, _| { + buffer_b.read_with(cx_b, |buffer, _| { assert_eq!( buffer.text(), "use d::SomeTrait;\nfn main() { a.first_method() }" @@ -960,7 +952,7 @@ async fn test_share_project( cx_c: &mut TestAppContext, ) { let executor = cx_a.executor(); - let window_b = cx_b.add_empty_window(); + let cx_b = cx_b.add_empty_window(); let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -1075,7 +1067,7 @@ async fn test_share_project( .await .unwrap(); - let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx)); + let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, cx)); // Client A sees client B's selection executor.run_until_parked(); @@ -1089,8 +1081,7 @@ async fn test_share_project( }); // Edit the buffer as client B and see that edit as client A. - let mut cx_b = VisualTestContext::from_window(window_b, cx_b); - editor_b.update(&mut cx_b, |editor, cx| editor.handle_input("ok, ", cx)); + editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx)); executor.run_until_parked(); buffer_a.read_with(cx_a, |buffer, _| { @@ -1099,7 +1090,7 @@ async fn test_share_project( // Client B can invite client C on a project shared by client A. active_call_b - .update(&mut cx_b, |call, cx| { + .update(cx_b, |call, cx| { call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx) }) .await @@ -1190,12 +1181,8 @@ async fn test_on_input_format_from_host_to_guest( .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let window_a = cx_a.add_empty_window(); - let editor_a = window_a - .update(cx_a, |_, cx| { - cx.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)) - }) - .unwrap(); + let cx_a = cx_a.add_empty_window(); + let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)); let fake_language_server = fake_language_servers.next().await.unwrap(); executor.run_until_parked(); @@ -1226,10 +1213,9 @@ async fn test_on_input_format_from_host_to_guest( .await .unwrap(); - let mut cx_a = VisualTestContext::from_window(window_a, cx_a); // Type a on type formatting trigger character as the guest. cx_a.focus_view(&editor_a); - editor_a.update(&mut cx_a, |editor, cx| { + editor_a.update(cx_a, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(">", cx); }); @@ -1241,7 +1227,7 @@ async fn test_on_input_format_from_host_to_guest( }); // Undo should remove LSP edits first - editor_a.update(&mut cx_a, |editor, cx| { + editor_a.update(cx_a, |editor, cx| { assert_eq!(editor.text(cx), "fn main() { a>~< }"); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "fn main() { a> }"); @@ -1252,7 +1238,7 @@ async fn test_on_input_format_from_host_to_guest( assert_eq!(buffer.text(), "fn main() { a> }") }); - editor_a.update(&mut cx_a, |editor, cx| { + editor_a.update(cx_a, |editor, cx| { assert_eq!(editor.text(cx), "fn main() { a> }"); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "fn main() { a }"); @@ -1323,17 +1309,15 @@ async fn test_on_input_format_from_guest_to_host( .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let window_b = cx_b.add_empty_window(); - let editor_b = window_b.build_view(cx_b, |cx| { - Editor::for_buffer(buffer_b, Some(project_b.clone()), cx) - }); + let cx_b = cx_b.add_empty_window(); + let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)); let fake_language_server = fake_language_servers.next().await.unwrap(); executor.run_until_parked(); - let mut cx_b = VisualTestContext::from_window(window_b, cx_b); + // Type a on type formatting trigger character as the guest. cx_b.focus_view(&editor_b); - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(":", cx); }); @@ -1374,7 +1358,7 @@ async fn test_on_input_format_from_guest_to_host( }); // Undo should remove LSP edits first - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { assert_eq!(editor.text(cx), "fn main() { a:~: }"); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "fn main() { a: }"); @@ -1385,7 +1369,7 @@ async fn test_on_input_format_from_guest_to_host( assert_eq!(buffer.text(), "fn main() { a: }") }); - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { assert_eq!(editor.text(cx), "fn main() { a: }"); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "fn main() { a }"); diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 556c104f69b9da1a20b32cb0fa325f5aeb975ce9..41cb722081b60b9b404244ed0e12e126dd63b279 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -568,6 +568,11 @@ pub struct VisualTestContext { } impl<'a> VisualTestContext { + /// Get the underlying window handle underlying this context. + pub fn handle(&self) -> AnyWindowHandle { + self.window + } + /// Provides the `WindowContext` for the duration of the closure. pub fn update(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R { self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index e217a7ab73cd2fd1aaa540f1b56cf13b7ec1c84b..ed2654de36508de7b20f2f522bc51b245354bd73 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1081,7 +1081,7 @@ mod tests { use super::*; use editor::{DisplayPoint, Editor}; - use gpui::{Context, EmptyView, Hsla, TestAppContext, VisualTestContext}; + use gpui::{Context, Hsla, TestAppContext, VisualTestContext}; use language::Buffer; use smol::stream::StreamExt as _; use unindent::Unindent as _; @@ -1114,7 +1114,7 @@ mod tests { .unindent(), ) }); - let (_, cx) = cx.add_window_view(|_| EmptyView {}); + let cx = cx.add_empty_window(); let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx)); let search_bar = cx.new_view(|cx| { @@ -1461,7 +1461,7 @@ mod tests { "Should pick a query with multiple results" ); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text)); - let window = cx.add_window(|_| EmptyView {}); + let window = cx.add_window(|_| ()); let editor = window.build_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx)); @@ -1657,7 +1657,7 @@ mod tests { "# .unindent(); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text)); - let (_, cx) = cx.add_window_view(|_| EmptyView {}); + let cx = cx.add_empty_window(); let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx)); From 80852c3e182ccdd4e1237b5e7770efa33ea9ec28 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 22:34:15 -0800 Subject: [PATCH 246/334] Add documentation to the new test --- crates/gpui/src/elements/list.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 96067d24000033e5c3b292eea3d70871887be7cc..9d49149e7abe7333f42abe5eb07b32e697098774 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -598,20 +598,30 @@ mod test { div().h(px(10.)).w_full().into_any() }); + // Ensure that the list is scrolled to the top + state.scroll_to(gpui::ListOffset { + item_ix: 0, + offset_in_item: px(0.0), + }); + + // Paint cx.draw( point(px(0.), px(0.)), size(px(100.), px(20.)).into(), |_| list(state.clone()).w_full().h_full().z_index(10).into_any(), ); + // Reset state.reset(5); + // And then recieve a scroll event _before_ the next paint cx.simulate_event(ScrollWheelEvent { position: point(px(1.), px(1.)), delta: ScrollDelta::Pixels(point(px(0.), px(-500.))), ..Default::default() }); + // Scroll position should stay at the top of the list assert_eq!(state.logical_scroll_top().item_ix, 0); assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); From 97bd3e1fde26deed91f6874058a365b97f54a742 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Jan 2024 09:45:46 +0100 Subject: [PATCH 247/334] Fix segfault caused by wrong size of path sprites bytes length Previously, we were using `size_of` but passing the wrong type in (MonochromeSprite instead of PathSprite). This caused us to read outside of the `sprites` smallvec and triggered the segfault. --- .../gpui/src/platform/mac/metal_renderer.rs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 36ded7ba8ecac63007e1e682caef624ee9766e3f..d3a32cc41d2c4b9d00d95a3b2292c7298a08f613 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -14,6 +14,7 @@ use foreign_types::ForeignType; use media::core_video::CVMetalTextureCache; use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; use objc::{self, msg_send, sel, sel_impl}; +use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); @@ -81,7 +82,7 @@ impl MetalRenderer { ]; let unit_vertices = device.new_buffer_with_data( unit_vertices.as_ptr() as *const c_void, - (unit_vertices.len() * mem::size_of::()) as u64, + mem::size_of_val(&unit_vertices) as u64, MTLResourceOptions::StorageModeManaged, ); let instances = device.new_buffer( @@ -339,7 +340,8 @@ impl MetalRenderer { for (texture_id, vertices) in vertices_by_texture_id { align_offset(offset); - let next_offset = *offset + vertices.len() * mem::size_of::>(); + let vertices_bytes_len = mem::size_of_val(vertices.as_slice()); + let next_offset = *offset + vertices_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return None; } @@ -372,7 +374,6 @@ impl MetalRenderer { &texture_size as *const Size as *const _, ); - let vertices_bytes_len = mem::size_of::>() * vertices.len(); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; unsafe { ptr::copy_nonoverlapping( @@ -429,7 +430,7 @@ impl MetalRenderer { &viewport_size as *const Size as *const _, ); - let shadow_bytes_len = std::mem::size_of_val(shadows); + let shadow_bytes_len = mem::size_of_val(shadows); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + shadow_bytes_len; @@ -490,7 +491,7 @@ impl MetalRenderer { &viewport_size as *const Size as *const _, ); - let quad_bytes_len = std::mem::size_of_val(quads); + let quad_bytes_len = mem::size_of_val(quads); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + quad_bytes_len; @@ -537,7 +538,7 @@ impl MetalRenderer { ); let mut prev_texture_id = None; - let mut sprites = Vec::new(); + let mut sprites = SmallVec::<[_; 1]>::new(); let mut paths_and_tiles = paths .iter() .map(|path| (path, tiles_by_path_id.get(&path.id).unwrap())) @@ -590,7 +591,7 @@ impl MetalRenderer { command_encoder .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); - let sprite_bytes_len = mem::size_of::() * sprites.len(); + let sprite_bytes_len = mem::size_of_val(sprites.as_slice()); let next_offset = *offset + sprite_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return false; @@ -655,17 +656,17 @@ impl MetalRenderer { &viewport_size as *const Size as *const _, ); - let quad_bytes_len = std::mem::size_of_val(underlines); + let underline_bytes_len = mem::size_of_val(underlines); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; unsafe { ptr::copy_nonoverlapping( underlines.as_ptr() as *const u8, buffer_contents, - quad_bytes_len, + underline_bytes_len, ); } - let next_offset = *offset + quad_bytes_len; + let next_offset = *offset + underline_bytes_len; if next_offset > INSTANCE_BUFFER_SIZE { return false; } @@ -726,7 +727,7 @@ impl MetalRenderer { ); command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); - let sprite_bytes_len = std::mem::size_of_val(sprites); + let sprite_bytes_len = mem::size_of_val(sprites); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; @@ -798,7 +799,7 @@ impl MetalRenderer { ); command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); - let sprite_bytes_len = std::mem::size_of_val(sprites); + let sprite_bytes_len = mem::size_of_val(sprites); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; From 9c337908099ec3c2033722af14245e4ff455e42c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Jan 2024 09:50:55 +0100 Subject: [PATCH 248/334] Check if we exhausted the instance buffer prior to copying underlines This fixes another potential segfault. --- crates/gpui/src/platform/mac/metal_renderer.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index d3a32cc41d2c4b9d00d95a3b2292c7298a08f613..1589757d935894ff676de7371fca97204d1ee63a 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -658,6 +658,12 @@ impl MetalRenderer { let underline_bytes_len = mem::size_of_val(underlines); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + underline_bytes_len; + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } + unsafe { ptr::copy_nonoverlapping( underlines.as_ptr() as *const u8, @@ -666,11 +672,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + underline_bytes_len; - if next_offset > INSTANCE_BUFFER_SIZE { - return false; - } - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, From 39dff0e82790c6d99b6c6a9362058d7b5749f63f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 17 Jan 2024 11:06:46 +0200 Subject: [PATCH 249/334] Stop using button for collab notifications --- crates/workspace/src/pane_group.rs | 74 ++++++++++++++---------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index ce58e51678c589c39e97666d71ccc097bc0a5324..b6e3b1a433d358db2856e060f140be0654c06baf 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -3,14 +3,14 @@ use anyhow::{anyhow, Result}; use call::{ActiveCall, ParticipantLocation}; use collections::HashMap; use gpui::{ - point, size, AnyView, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, + point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels, Point, View, ViewContext, }; use parking_lot::Mutex; use project::Project; use serde::Deserialize; use std::sync::Arc; -use ui::{prelude::*, Button}; +use ui::prelude::*; pub const HANDLE_HITBOX_SIZE: f32 = 4.0; const HORIZONTAL_MIN_SIZE: f32 = 80.; @@ -183,6 +183,7 @@ impl Member { let mut leader_border = None; let mut leader_status_box = None; + let mut leader_join_data = None; if let Some(leader) = &leader { let mut leader_color = cx .theme() @@ -199,44 +200,21 @@ impl Member { if Some(leader_project_id) == project.read(cx).remote_id() { None } else { - let leader_user = leader.user.clone(); - let leader_user_id = leader.user.id; - Some( - Button::new( - ("leader-status", pane.entity_id()), - format!( - "Follow {} to their active project", - leader_user.github_login, - ), - ) - .on_click(cx.listener( - move |this, _, cx| { - crate::join_remote_project( - leader_project_id, - leader_user_id, - this.app_state().clone(), - cx, - ) - .detach_and_log_err(cx); - }, - )), - ) + leader_join_data = Some((leader_project_id, leader.user.id)); + Some(Label::new(format!( + "Follow {} to their active project", + leader.user.github_login, + ))) } } - ParticipantLocation::UnsharedProject => Some(Button::new( - ("leader-status", pane.entity_id()), - format!( - "{} is viewing an unshared Zed project", - leader.user.github_login - ), - )), - ParticipantLocation::External => Some(Button::new( - ("leader-status", pane.entity_id()), - format!( - "{} is viewing a window outside of Zed", - leader.user.github_login - ), - )), + ParticipantLocation::UnsharedProject => Some(Label::new(format!( + "{} is viewing an unshared Zed project", + leader.user.github_login + ))), + ParticipantLocation::External => Some(Label::new(format!( + "{} is viewing a window outside of Zed", + leader.user.github_login + ))), }; } @@ -264,7 +242,25 @@ impl Member { .bottom_3() .right_3() .z_index(1) - .child(status_box), + .bg(cx.theme().colors().element_background) + .child(status_box) + .when_some( + leader_join_data, + |this, (leader_project_id, leader_user_id)| { + this.cursor_pointer().on_mouse_down( + MouseButton::Left, + cx.listener(move |this, _, cx| { + crate::join_remote_project( + leader_project_id, + leader_user_id, + this.app_state().clone(), + cx, + ) + .detach_and_log_err(cx); + }), + ) + }, + ), ) }) .into_any() From 04922d649ccfaf149fa77752ae3d862ccc92d38d Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Wed, 17 Jan 2024 11:07:20 +0100 Subject: [PATCH 250/334] Fix missing Ctrl-[ bindings in Vim mode This "adds" the keybindings I was missing in Vim mode (e.g. `Ctrl-[` to cancel a selection) by fixing the definitions in the keymap from `Ctrl+[` to `Ctrl-[`. --- assets/keymaps/vim.json | 8 ++++---- crates/vim/src/test.rs | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 81235bb72ad7075be27605c462fb03b822b69140..1da6f0ef8c5da25a60742fda933125c23eac81bf 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -99,7 +99,7 @@ "ctrl-i": "pane::GoForward", "ctrl-]": "editor::GoToDefinition", "escape": ["vim::SwitchMode", "Normal"], - "ctrl+[": ["vim::SwitchMode", "Normal"], + "ctrl-[": ["vim::SwitchMode", "Normal"], "v": "vim::ToggleVisual", "shift-v": "vim::ToggleVisualLine", "ctrl-v": "vim::ToggleVisualBlock", @@ -288,7 +288,7 @@ "context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting", "bindings": { "escape": "editor::Cancel", - "ctrl+[": "editor::Cancel" + "ctrl-[": "editor::Cancel" } }, { @@ -441,7 +441,7 @@ "r": ["vim::PushOperator", "Replace"], "ctrl-c": ["vim::SwitchMode", "Normal"], "escape": ["vim::SwitchMode", "Normal"], - "ctrl+[": ["vim::SwitchMode", "Normal"], + "ctrl-[": ["vim::SwitchMode", "Normal"], ">": "editor::Indent", "<": "editor::Outdent", "i": [ @@ -481,7 +481,7 @@ "tab": "vim::Tab", "enter": "vim::Enter", "escape": ["vim::SwitchMode", "Normal"], - "ctrl+[": ["vim::SwitchMode", "Normal"] + "ctrl-[": ["vim::SwitchMode", "Normal"] } }, { diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index b23c49c9a3fd4f23d1d547f7165df64cb33c1f75..fa2dcb45cda61f4637b5bcb6ac0cd1d43236def3 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -71,6 +71,30 @@ async fn test_toggle_through_settings(cx: &mut gpui::TestAppContext) { assert_eq!(cx.mode(), Mode::Normal); } +#[gpui::test] +async fn test_cancel_selection(cx: &mut gpui::TestAppContext) { + let mut cx = VimTestContext::new(cx, true).await; + + cx.set_state( + indoc! {"The quick brown fox juˇmps over the lazy dog"}, + Mode::Normal, + ); + // jumps + cx.simulate_keystrokes(["v", "l", "l"]); + cx.assert_editor_state("The quick brown fox ju«mpsˇ» over the lazy dog"); + + cx.simulate_keystrokes(["escape"]); + cx.assert_editor_state("The quick brown fox jumpˇs over the lazy dog"); + + // go back to the same selection state + cx.simulate_keystrokes(["v", "h", "h"]); + cx.assert_editor_state("The quick brown fox ju«ˇmps» over the lazy dog"); + + // Ctrl-[ should behave like Esc + cx.simulate_keystrokes(["ctrl-["]); + cx.assert_editor_state("The quick brown fox juˇmps over the lazy dog"); +} + #[gpui::test] async fn test_buffer_search(cx: &mut gpui::TestAppContext) { let mut cx = VimTestContext::new(cx, true).await; From 5b0b9ff5828251e8e85b698bfb0a4437d64e2df6 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Wed, 17 Jan 2024 13:50:55 +0100 Subject: [PATCH 251/334] Submit bigger primitive batches when rendering Before this change we wouldn't submit all possible primitives of the same kind that are less-than the max order. Result was that we would submit, say, 10 paths each in a separate batch instead of actually batching them. This was overly strict because even if the order of two different primitives was the same, we could have still batched the 1st primitive kind, if its implicit ordering was less than 2nd kind. Example: say we have the following primitives and these orders 5x paths, order 3 2x sprites, order 3 Previously, we would submit 1 path, 1 path, 1 path, 1 path, 1 path, then the sprites. With this changes, we batch the 5 paths into one batch. Co-authored-by: Antonio --- crates/gpui/src/scene.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 11341a2cbc524899a3455f490b20652e7f17fc4b..de031704cd2888917534083bffd90e00c7e8b49b 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -299,8 +299,8 @@ impl<'a> Iterator for BatchIterator<'a> { let first = orders_and_kinds[0]; let second = orders_and_kinds[1]; - let (batch_kind, max_order) = if first.0.is_some() { - (first.1, second.0.unwrap_or(u32::MAX)) + let (batch_kind, max_order_and_kind) = if first.0.is_some() { + (first.1, (second.0.unwrap_or(u32::MAX), second.1)) } else { return None; }; @@ -312,7 +312,7 @@ impl<'a> Iterator for BatchIterator<'a> { self.shadows_iter.next(); while self .shadows_iter - .next_if(|shadow| shadow.order < max_order) + .next_if(|shadow| (shadow.order, batch_kind) < max_order_and_kind) .is_some() { shadows_end += 1; @@ -328,7 +328,7 @@ impl<'a> Iterator for BatchIterator<'a> { self.quads_iter.next(); while self .quads_iter - .next_if(|quad| quad.order < max_order) + .next_if(|quad| (quad.order, batch_kind) < max_order_and_kind) .is_some() { quads_end += 1; @@ -342,7 +342,7 @@ impl<'a> Iterator for BatchIterator<'a> { self.paths_iter.next(); while self .paths_iter - .next_if(|path| path.order < max_order) + .next_if(|path| (path.order, batch_kind) < max_order_and_kind) .is_some() { paths_end += 1; @@ -356,7 +356,7 @@ impl<'a> Iterator for BatchIterator<'a> { self.underlines_iter.next(); while self .underlines_iter - .next_if(|underline| underline.order < max_order) + .next_if(|underline| (underline.order, batch_kind) < max_order_and_kind) .is_some() { underlines_end += 1; @@ -374,7 +374,8 @@ impl<'a> Iterator for BatchIterator<'a> { while self .monochrome_sprites_iter .next_if(|sprite| { - sprite.order < max_order && sprite.tile.texture_id == texture_id + (sprite.order, batch_kind) < max_order_and_kind + && sprite.tile.texture_id == texture_id }) .is_some() { @@ -394,7 +395,8 @@ impl<'a> Iterator for BatchIterator<'a> { while self .polychrome_sprites_iter .next_if(|sprite| { - sprite.order < max_order && sprite.tile.texture_id == texture_id + (sprite.order, batch_kind) < max_order_and_kind + && sprite.tile.texture_id == texture_id }) .is_some() { @@ -412,7 +414,7 @@ impl<'a> Iterator for BatchIterator<'a> { self.surfaces_iter.next(); while self .surfaces_iter - .next_if(|surface| surface.order < max_order) + .next_if(|surface| (surface.order, batch_kind) < max_order_and_kind) .is_some() { surfaces_end += 1; From 51127460b2ad235e8173d2e20f475a18a36f127f Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Wed, 17 Jan 2024 15:01:32 +0100 Subject: [PATCH 252/334] Remove memmove to improve terminal performance Co-authored-by: Antonio --- crates/terminal_view/src/terminal_element.rs | 109 +++++++++---------- 1 file changed, 53 insertions(+), 56 deletions(-) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 1fec041de9c633493993696cdc6fdda142355c77..6298b4c16a07b47054430a6e2dcacd9e4f6e033e 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -250,8 +250,8 @@ impl TerminalElement { //Layout current cell text { - let cell_text = cell.c.to_string(); if !is_blank(&cell) { + let cell_text = cell.c.to_string(); let cell_style = TerminalElement::cell_style(&cell, fg, theme, text_style, hyperlink); @@ -586,24 +586,6 @@ impl TerminalElement { } } - fn register_key_listeners(&self, cx: &mut WindowContext) { - cx.on_key_event({ - let this = self.terminal.clone(); - move |event: &ModifiersChangedEvent, phase, cx| { - if phase != DispatchPhase::Bubble { - return; - } - - let handled = - this.update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); - - if handled { - cx.refresh(); - } - } - }); - } - fn register_mouse_listeners( &mut self, origin: Point, @@ -771,53 +753,68 @@ impl Element for TerminalElement { self.register_mouse_listeners(origin, layout.mode, bounds, cx); - let mut interactivity = mem::take(&mut self.interactivity); - interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { - cx.handle_input(&self.focus, terminal_input_handler); + self.interactivity + .paint(bounds, bounds.size, state, cx, |_, _, cx| { + cx.handle_input(&self.focus, terminal_input_handler); - self.register_key_listeners(cx); + cx.on_key_event({ + let this = self.terminal.clone(); + move |event: &ModifiersChangedEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } - for rect in &layout.rects { - rect.paint(origin, &layout, cx); - } + let handled = + this.update(cx, |term, _| term.try_modifiers_change(&event.modifiers)); - cx.with_z_index(1, |cx| { - for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter() - { - if let Some((start_y, highlighted_range_lines)) = - to_highlighted_range_lines(relative_highlighted_range, &layout, origin) - { - let hr = HighlightedRange { - start_y, //Need to change this - line_height: layout.dimensions.line_height, - lines: highlighted_range_lines, - color: color.clone(), - //Copied from editor. TODO: move to theme or something - corner_radius: 0.15 * layout.dimensions.line_height, - }; - hr.paint(bounds, cx); + if handled { + cx.refresh(); + } } - } - }); + }); - cx.with_z_index(2, |cx| { - for cell in &layout.cells { - cell.paint(origin, &layout, bounds, cx); + for rect in &layout.rects { + rect.paint(origin, &layout, cx); } - }); - if self.cursor_visible { - cx.with_z_index(3, |cx| { - if let Some(cursor) = &layout.cursor { - cursor.paint(origin, cx); + cx.with_z_index(1, |cx| { + for (relative_highlighted_range, color) in + layout.relative_highlighted_ranges.iter() + { + if let Some((start_y, highlighted_range_lines)) = + to_highlighted_range_lines(relative_highlighted_range, &layout, origin) + { + let hr = HighlightedRange { + start_y, //Need to change this + line_height: layout.dimensions.line_height, + lines: highlighted_range_lines, + color: color.clone(), + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * layout.dimensions.line_height, + }; + hr.paint(bounds, cx); + } } }); - } - if let Some(mut element) = layout.hyperlink_tooltip.take() { - element.draw(origin, bounds.size.map(AvailableSpace::Definite), cx) - } - }); + cx.with_z_index(2, |cx| { + for cell in &layout.cells { + cell.paint(origin, &layout, bounds, cx); + } + }); + + if self.cursor_visible { + cx.with_z_index(3, |cx| { + if let Some(cursor) = &layout.cursor { + cursor.paint(origin, cx); + } + }); + } + + if let Some(mut element) = layout.hyperlink_tooltip.take() { + element.draw(origin, bounds.size.map(AvailableSpace::Definite), cx) + } + }); } } From 977832a04ea03b2de94a6bb979e43f548cce46fc Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 17 Jan 2024 09:40:16 -0500 Subject: [PATCH 253/334] Refresh window, bypassing view cache, when opening hover or context menu --- crates/editor/src/hover_popover.rs | 1 + crates/ui/src/components/context_menu.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 8da2f50c198a01136d1318922a5159e3814a894f..609c20ac680819ff738f4a4f3ab08ffa58762146 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -339,6 +339,7 @@ fn show_hover( this.hover_state.info_popover = hover_popover; cx.notify(); + cx.refresh(); })?; Ok::<_, anyhow::Error>(()) diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 5c4f110a415b2e1e8ef0ac2d36b79d7d8bc2b4be..4b6837799976579b6bc469753c51cb964e49e7b0 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -51,6 +51,7 @@ impl ContextMenu { let _on_blur_subscription = cx.on_blur(&focus_handle, |this: &mut ContextMenu, cx| { this.cancel(&menu::Cancel, cx) }); + cx.refresh(); f( Self { items: Default::default(), From a601e96b6cf4e2612c7903d7ae4ff7b7c2d0faa0 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 17 Jan 2024 16:44:43 +0200 Subject: [PATCH 254/334] Style collab notifications properly --- crates/workspace/src/pane_group.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index b6e3b1a433d358db2856e060f140be0654c06baf..e631cd9c436b6918a974d2afdcc7ac9d4aa37520 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -241,8 +241,9 @@ impl Member { .w_96() .bottom_3() .right_3() + .elevation_2(cx) + .p_1() .z_index(1) - .bg(cx.theme().colors().element_background) .child(status_box) .when_some( leader_join_data, From 9c557aae9e6963b30748d25852d8a04c03521405 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Jan 2024 11:00:59 -0500 Subject: [PATCH 255/334] Fix regression of welcome screen background color (#4091) In #3910 we made the welcome screen use the same background color as the editor. However, this later regressed in cdd5cb16ed896b2ee3bbb041983ee7cb812f6991. This PR fixes that regression and restores the correct color for the welcome page. Release Notes: - Fixed the background color of the welcome screen. --- crates/welcome/src/welcome.rs | 361 +++++++++++++++++----------------- 1 file changed, 184 insertions(+), 177 deletions(-) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 677b57a0225059430b141ca23573d42433f52b77..53b78b917fec1d43c8c194a1b1bf0ee8198fe2a5 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -60,190 +60,197 @@ pub struct WelcomePage { impl Render for WelcomePage { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { - h_flex().full().track_focus(&self.focus_handle).child( - v_flex() - .w_96() - .gap_4() - .mx_auto() - .child( - svg() - .path("icons/logo_96.svg") - .text_color(gpui::white()) - .w(px(96.)) - .h(px(96.)) - .mx_auto(), - ) - .child( - h_flex() - .justify_center() - .child(Label::new("Code at the speed of thought")), - ) - .child( - v_flex() - .gap_2() - .child( - Button::new("choose-theme", "Choose a theme") - .full_width() - .on_click(cx.listener(|this, _, cx| { - this.telemetry - .report_app_event("welcome page: change theme".to_string()); - this.workspace - .update(cx, |workspace, cx| { - theme_selector::toggle( - workspace, - &Default::default(), - cx, - ) - }) - .ok(); - })), - ) - .child( - Button::new("choose-keymap", "Choose a keymap") - .full_width() - .on_click(cx.listener(|this, _, cx| { - this.telemetry.report_app_event( - "welcome page: change keymap".to_string(), - ); - this.workspace - .update(cx, |workspace, cx| { - base_keymap_picker::toggle( - workspace, - &Default::default(), - cx, - ) - }) - .ok(); - })), - ) - .child( - Button::new("install-cli", "Install the CLI") - .full_width() - .on_click(cx.listener(|this, _, cx| { - this.telemetry - .report_app_event("welcome page: install cli".to_string()); - cx.app_mut() - .spawn( - |cx| async move { install_cli::install_cli(&cx).await }, + h_flex() + .full() + .bg(cx.theme().colors().editor_background) + .track_focus(&self.focus_handle) + .child( + v_flex() + .w_96() + .gap_4() + .mx_auto() + .child( + svg() + .path("icons/logo_96.svg") + .text_color(gpui::white()) + .w(px(96.)) + .h(px(96.)) + .mx_auto(), + ) + .child( + h_flex() + .justify_center() + .child(Label::new("Code at the speed of thought")), + ) + .child( + v_flex() + .gap_2() + .child( + Button::new("choose-theme", "Choose a theme") + .full_width() + .on_click(cx.listener(|this, _, cx| { + this.telemetry.report_app_event( + "welcome page: change theme".to_string(), + ); + this.workspace + .update(cx, |workspace, cx| { + theme_selector::toggle( + workspace, + &Default::default(), + cx, + ) + }) + .ok(); + })), + ) + .child( + Button::new("choose-keymap", "Choose a keymap") + .full_width() + .on_click(cx.listener(|this, _, cx| { + this.telemetry.report_app_event( + "welcome page: change keymap".to_string(), + ); + this.workspace + .update(cx, |workspace, cx| { + base_keymap_picker::toggle( + workspace, + &Default::default(), + cx, + ) + }) + .ok(); + })), + ) + .child( + Button::new("install-cli", "Install the CLI") + .full_width() + .on_click(cx.listener(|this, _, cx| { + this.telemetry.report_app_event( + "welcome page: install cli".to_string(), + ); + cx.app_mut() + .spawn(|cx| async move { + install_cli::install_cli(&cx).await + }) + .detach_and_log_err(cx); + })), + ), + ) + .child( + v_flex() + .p_3() + .gap_2() + .bg(cx.theme().colors().elevated_surface_background) + .border_1() + .border_color(cx.theme().colors().border) + .rounded_md() + .child( + h_flex() + .gap_2() + .child( + Checkbox::new( + "enable-vim", + if VimModeSetting::get_global(cx).0 { + ui::Selection::Selected + } else { + ui::Selection::Unselected + }, ) - .detach_and_log_err(cx); - })), - ), - ) - .child( - v_flex() - .p_3() - .gap_2() - .bg(cx.theme().colors().elevated_surface_background) - .border_1() - .border_color(cx.theme().colors().border) - .rounded_md() - .child( - h_flex() - .gap_2() - .child( - Checkbox::new( - "enable-vim", - if VimModeSetting::get_global(cx).0 { - ui::Selection::Selected - } else { - ui::Selection::Unselected - }, + .on_click( + cx.listener(move |this, selection, cx| { + this.telemetry.report_app_event( + "welcome page: toggle vim".to_string(), + ); + this.update_settings::( + selection, + cx, + |setting, value| *setting = Some(value), + ); + }), + ), ) - .on_click(cx.listener( - move |this, selection, cx| { - this.telemetry.report_app_event( - "welcome page: toggle vim".to_string(), - ); - this.update_settings::( - selection, - cx, - |setting, value| *setting = Some(value), - ); - }, - )), - ) - .child(Label::new("Enable vim mode")), - ) - .child( - h_flex() - .gap_2() - .child( - Checkbox::new( - "enable-telemetry", - if TelemetrySettings::get_global(cx).metrics { - ui::Selection::Selected - } else { - ui::Selection::Unselected - }, - ) - .on_click(cx.listener( - move |this, selection, cx| { - this.telemetry.report_app_event( - "welcome page: toggle metric telemetry".to_string(), - ); - this.update_settings::( - selection, - cx, - { - let telemetry = this.telemetry.clone(); + .child(Label::new("Enable vim mode")), + ) + .child( + h_flex() + .gap_2() + .child( + Checkbox::new( + "enable-telemetry", + if TelemetrySettings::get_global(cx).metrics { + ui::Selection::Selected + } else { + ui::Selection::Unselected + }, + ) + .on_click( + cx.listener(move |this, selection, cx| { + this.telemetry.report_app_event( + "welcome page: toggle metric telemetry" + .to_string(), + ); + this.update_settings::( + selection, + cx, + { + let telemetry = this.telemetry.clone(); - move |settings, value| { - settings.metrics = Some(value); + move |settings, value| { + settings.metrics = Some(value); - telemetry.report_setting_event( - "metric telemetry", - value.to_string(), - ); - } - }, - ); - }, - )), - ) - .child(Label::new("Send anonymous usage data")), - ) - .child( - h_flex() - .gap_2() - .child( - Checkbox::new( - "enable-crash", - if TelemetrySettings::get_global(cx).diagnostics { - ui::Selection::Selected - } else { - ui::Selection::Unselected - }, + telemetry.report_setting_event( + "metric telemetry", + value.to_string(), + ); + } + }, + ); + }), + ), ) - .on_click(cx.listener( - move |this, selection, cx| { - this.telemetry.report_app_event( - "welcome page: toggle diagnostic telemetry" - .to_string(), - ); - this.update_settings::( - selection, - cx, - { - let telemetry = this.telemetry.clone(); + .child(Label::new("Send anonymous usage data")), + ) + .child( + h_flex() + .gap_2() + .child( + Checkbox::new( + "enable-crash", + if TelemetrySettings::get_global(cx).diagnostics { + ui::Selection::Selected + } else { + ui::Selection::Unselected + }, + ) + .on_click( + cx.listener(move |this, selection, cx| { + this.telemetry.report_app_event( + "welcome page: toggle diagnostic telemetry" + .to_string(), + ); + this.update_settings::( + selection, + cx, + { + let telemetry = this.telemetry.clone(); - move |settings, value| { - settings.diagnostics = Some(value); + move |settings, value| { + settings.diagnostics = Some(value); - telemetry.report_setting_event( - "diagnostic telemetry", - value.to_string(), - ); - } - }, - ); - }, - )), - ) - .child(Label::new("Send crash reports")), - ), - ), - ) + telemetry.report_setting_event( + "diagnostic telemetry", + value.to_string(), + ); + } + }, + ); + }), + ), + ) + .child(Label::new("Send crash reports")), + ), + ), + ) } } From 4cdcac1b16da71692802cf72c1b2abab41f5e58e Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 17 Jan 2024 11:39:09 -0500 Subject: [PATCH 256/334] Update docs --- crates/color/src/color.rs | 93 ++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/crates/color/src/color.rs b/crates/color/src/color.rs index d3d832099af16d85f2afd0e5e6551bca966618c6..8529f3bc5feea6b3248875e412914dc24b39a4b5 100644 --- a/crates/color/src/color.rs +++ b/crates/color/src/color.rs @@ -4,25 +4,49 @@ //! //! It is used to create a manipulate colors when building themes. //! -//! **Note:** This crate does not depend on `gpui`, so it does not provide any -//! interfaces for converting to `gpui` style colors. - +//! === In development note === +//! +//! This crate is meant to sit between gpui and the theme/ui for all the color related stuff. +//! +//! It could be folded into gpui, ui or theme potentially but for now we'll continue +//! to develop it in isolation. +//! +//! Once we have a good idea of the needs of the theme system and color in gpui in general I see 3 paths: +//! 1. Use `palette` (or another color library) directly in gpui and everywhere else, rather than rolling our own color system. +//! 2. Keep this crate as a thin wrapper around `palette` and use it everywhere except gpui, and convert to gpui's color system when needed. +//! 3. Build the needed functionality into gpui and keep using it's color system everywhere. +//! +//! I'm leaning towards 2 in the short term and 1 in the long term, but we'll need to discuss it more. +//! +//! === End development note === use palette::{ blend::Blend, convert::FromColorUnclamped, encoding, rgb::Rgb, Clamp, Mix, Srgb, WithAlpha, }; +/// The types of blend modes supported #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum BlendMode { + /// Multiplies the colors, resulting in a darker color. This mode is useful for creating shadows. Multiply, + /// Lightens the color by adding the source and destination colors. It results in a lighter color. Screen, + /// Combines Multiply and Screen blend modes. Parts of the image that are lighter than 50% gray are lightened, and parts that are darker are darkened. Overlay, + /// Selects the darker of the base or blend color as the resulting color. Useful for darkening images without affecting the overall contrast. Darken, + /// Selects the lighter of the base or blend color as the resulting color. Useful for lightening images without affecting the overall contrast. Lighten, + /// Brightens the base color to reflect the blend color. The result is a lightened image. Dodge, + /// Darkens the base color to reflect the blend color. The result is a darkened image. Burn, + /// Similar to Overlay, but with a stronger effect. Hard Light can either multiply or screen colors, depending on the blend color. HardLight, + /// A softer version of Hard Light. Soft Light either darkens or lightens colors, depending on the blend color. SoftLight, + /// Subtracts the darker of the two constituent colors from the lighter color. Difference mode is useful for creating more vivid colors. Difference, + /// Similar to Difference, but with a lower contrast. Exclusion mode produces an effect similar to Difference but with less intensity. Exclusion, } @@ -30,7 +54,7 @@ pub enum BlendMode { /// /// This function supports the following hex formats: /// `#RGB`, `#RGBA`, `#RRGGBB`, `#RRGGBBAA`. -pub fn hex_to_hsla(s: &str) -> Result { +pub fn hex_to_hsla(s: &str) -> Result { let hex = s.trim_start_matches('#'); // Expand shorthand formats #RGB and #RGBA to #RRGGBB and #RRGGBBAA @@ -61,16 +85,15 @@ pub fn hex_to_hsla(s: &str) -> Result { let b = ((hex_val >> 8) & 0xFF) as f32 / 255.0; let a = (hex_val & 0xFF) as f32 / 255.0; - let color = Color { r, g, b, a }; + let color = RGBAColor { r, g, b, a }; Ok(color) } -// This implements conversion to and from all Palette colors. +// These derives implement to and from palette's color types. #[derive(FromColorUnclamped, WithAlpha, Debug, Clone)] -// We have to tell Palette that we will take care of converting to/from sRGB. #[palette(skip_derives(Rgb), rgb_standard = "encoding::Srgb")] -pub struct Color { +pub struct RGBAColor { r: f32, g: f32, b: f32, @@ -79,22 +102,19 @@ pub struct Color { a: f32, } -// There's no blanket implementation for Self -> Self, unlike the From trait. -// This is to better allow cases like Self -> Self. -impl FromColorUnclamped for Color { - fn from_color_unclamped(color: Color) -> Color { +impl FromColorUnclamped for RGBAColor { + fn from_color_unclamped(color: RGBAColor) -> RGBAColor { color } } -// Convert from any kind of f32 sRGB. -impl FromColorUnclamped> for Color +impl FromColorUnclamped> for RGBAColor where Srgb: FromColorUnclamped>, { - fn from_color_unclamped(color: Rgb) -> Color { + fn from_color_unclamped(color: Rgb) -> RGBAColor { let srgb = Srgb::from_color_unclamped(color); - Color { + RGBAColor { r: srgb.red, g: srgb.green, b: srgb.blue, @@ -103,21 +123,19 @@ where } } -// Convert into any kind of f32 sRGB. -impl FromColorUnclamped for Rgb +impl FromColorUnclamped for Rgb where Rgb: FromColorUnclamped, { - fn from_color_unclamped(color: Color) -> Self { + fn from_color_unclamped(color: RGBAColor) -> Self { let srgb = Srgb::new(color.r, color.g, color.b); Self::from_color_unclamped(srgb) } } -// Add the required clamping. -impl Clamp for Color { +impl Clamp for RGBAColor { fn clamp(self) -> Self { - Color { + RGBAColor { r: self.r.min(1.0).max(0.0), g: self.g.min(1.0).max(0.0), b: self.b.min(1.0).max(0.0), @@ -126,9 +144,12 @@ impl Clamp for Color { } } -impl Color { +impl RGBAColor { + /// Creates a new color from the given RGBA values. + /// + /// This color can be used to convert to any [`palette::Color`] type. pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { - Color { r, g, b, a } + RGBAColor { r, g, b, a } } /// Returns a set of states for this color. @@ -137,16 +158,16 @@ impl Color { } /// Mixes this color with another [`palette::Hsl`] color at the given `mix_ratio`. - pub fn mixed(&self, other: Color, mix_ratio: f32) -> Self { + pub fn mixed(&self, other: RGBAColor, mix_ratio: f32) -> Self { let srgb_self = Srgb::new(self.r, self.g, self.b); let srgb_other = Srgb::new(other.r, other.g, other.b); // Directly mix the colors as sRGB values let mixed = srgb_self.mix(srgb_other, mix_ratio); - Color::from_color_unclamped(mixed) + RGBAColor::from_color_unclamped(mixed) } - pub fn blend(&self, other: Color, blend_mode: BlendMode) -> Self { + pub fn blend(&self, other: RGBAColor, blend_mode: BlendMode) -> Self { let srgb_self = Srgb::new(self.r, self.g, self.b); let srgb_other = Srgb::new(other.r, other.g, other.b); @@ -169,31 +190,31 @@ impl Color { #[derive(Debug, Clone)] pub struct ColorStates { /// The default color. - pub default: Color, + pub default: RGBAColor, /// The color when the mouse is hovering over the element. - pub hover: Color, + pub hover: RGBAColor, /// The color when the mouse button is held down on the element. - pub active: Color, + pub active: RGBAColor, /// The color when the element is focused with the keyboard. - pub focused: Color, + pub focused: RGBAColor, /// The color when the element is disabled. - pub disabled: Color, + pub disabled: RGBAColor, } /// Returns a set of colors for different states of an element. /// -/// todo!("Test and improve this function") -pub fn states_for_color(color: Color, is_light: bool) -> ColorStates { +/// todo!("This should take a theme and use appropriate colors from it") +pub fn states_for_color(color: RGBAColor, is_light: bool) -> ColorStates { let adjustment_factor = if is_light { 0.1 } else { -0.1 }; let hover_adjustment = 1.0 - adjustment_factor; let active_adjustment = 1.0 - 2.0 * adjustment_factor; let focused_adjustment = 1.0 - 3.0 * adjustment_factor; let disabled_adjustment = 1.0 - 4.0 * adjustment_factor; - let make_adjustment = |color: Color, adjustment: f32| -> Color { + let make_adjustment = |color: RGBAColor, adjustment: f32| -> RGBAColor { // Adjust lightness for each state // Note: Adjustment logic may differ; simplify as needed for sRGB - Color::new( + RGBAColor::new( color.r * adjustment, color.g * adjustment, color.b * adjustment, From df67917768b4fef7e6a30472ecba4403622be8ce Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Jan 2024 11:47:43 -0500 Subject: [PATCH 257/334] Make channel buttons square (#4092) This PR makes the channel buttons square. Release Notes: - Adjusted the shape of the channel buttons. --- crates/collab_ui/src/collab_panel.rs | 4 +-- .../ui/src/components/button/icon_button.rs | 17 +++++++--- .../ui/src/components/stories/icon_button.rs | 31 +++++++++++++++++-- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 47c5463e921eee93bfc4d1032dd74808ffb21fe7..d6de5135711b7e56854eaa541afc6b23a3020544 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2314,7 +2314,7 @@ impl CollabPanel { .child( IconButton::new("channel_chat", IconName::MessageBubbles) .style(ButtonStyle::Filled) - .size(ButtonSize::Compact) + .shape(ui::IconButtonShape::Square) .icon_size(IconSize::Small) .icon_color(if has_messages_notification { Color::Default @@ -2332,7 +2332,7 @@ impl CollabPanel { .child( IconButton::new("channel_notes", IconName::File) .style(ButtonStyle::Filled) - .size(ButtonSize::Compact) + .shape(ui::IconButtonShape::Square) .icon_size(IconSize::Small) .icon_color(if has_notes_notification { Color::Default diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index 1e37a872922b4473616b551352f8100bbd9b1327..cc1e31b65cb0836be9a307107302ba8705597d6c 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -127,16 +127,25 @@ impl VisibleOnHover for IconButton { } impl RenderOnce for IconButton { - fn render(self, _cx: &mut WindowContext) -> impl IntoElement { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { let is_disabled = self.base.disabled; let is_selected = self.base.selected; let selected_style = self.base.selected_style; self.base .map(|this| match self.shape { - IconButtonShape::Square => this - .width(self.icon_size.rems().into()) - .height(self.icon_size.rems().into()), + IconButtonShape::Square => { + let icon_size = self.icon_size.rems() * cx.rem_size(); + let padding = match self.icon_size { + IconSize::Indicator => px(0.), + IconSize::XSmall => px(0.), + IconSize::Small => px(2.), + IconSize::Medium => px(2.), + }; + + this.width((icon_size + padding * 2.).into()) + .height((icon_size + padding * 2.).into()) + } IconButtonShape::Wide => this, }) .child( diff --git a/crates/ui/src/components/stories/icon_button.rs b/crates/ui/src/components/stories/icon_button.rs index df9f37b164782f35c9a2ca1cfba6aa96d8783d60..ba3d5fd8660988298a38307cb8a690d1a365c7f4 100644 --- a/crates/ui/src/components/stories/icon_button.rs +++ b/crates/ui/src/components/stories/icon_button.rs @@ -1,7 +1,7 @@ use gpui::Render; use story::{StoryContainer, StoryItem, StorySection}; -use crate::{prelude::*, Tooltip}; +use crate::{prelude::*, IconButtonShape, Tooltip}; use crate::{IconButton, IconName}; pub struct IconButtonStory; @@ -115,7 +115,34 @@ impl Render for IconButtonStory { "Icon Button", "crates/ui2/src/components/stories/icon_button.rs", ) - .children(vec![StorySection::new().children(buttons)]) + .child(StorySection::new().children(buttons)) + .child( + StorySection::new().child(StoryItem::new( + "Square", + h_flex() + .gap_2() + .child( + IconButton::new("square-medium", IconName::Close) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Medium), + ) + .child( + IconButton::new("square-small", IconName::Close) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small), + ) + .child( + IconButton::new("square-xsmall", IconName::Close) + .shape(IconButtonShape::Square) + .icon_size(IconSize::XSmall), + ) + .child( + IconButton::new("square-indicator", IconName::Close) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Indicator), + ), + )), + ) .into_element() } } From 904695e4825b264082c8edcd322d349dcc67da08 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 17 Jan 2024 12:32:08 -0500 Subject: [PATCH 258/334] Refine MVP CONTRIBUTING.md Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com> --- CONTRIBUTING.md | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 825ddce79d2611bf1e48f4e2fcc11bdc98beeb6b..0c45475e4ca8ccbfbd1b22b9bc6270ca34c28cd3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,16 +4,18 @@ Thanks for your interest in contributing to Zed, the collaborative platform that We want to ensure that no one ends up spending time on a pull request that may not be accepted, so we ask that you discuss your ideas with the team and community before starting on a contribution. -All activity in Zed communities is subject to our [Code of Conduct](https://docs.zed.dev/community/code-of-conduct). Contributors to Zed must sign our [Contributor License Agreement TODO](LINK) before their contributions can be merged. +All activity in Zed communities is subject to our [Code of Conduct](https://docs.zed.dev/community/code-of-conduct). Contributors to Zed must sign our Contributor License Agreement (link coming soon) before their contributions can be merged. ## Contribution ideas If you already have an idea of what you'd like to contribute, you can skip this section, otherwise, here are a few resources to help you find something to work on: -- Our [public roadmap TODO](LINK) details what features we plan to add to Zed. +- Our public roadmap (link coming soon!) details what features we plan to add to Zed. - Our [Top-Ranking Issues issue](https://github.com/zed-industries/community/issues/52) shows the most popular feature requests and issues, as voted on by the community. -At the moment, we are generally not looking to extend Zed's language or theme support by directly adding these features to Zed - we really want to build a plugin system to handle making the editor extensible going forward. This isn't to say that we won't accept contributions that add support for a new language or theme, but more to emphasize that we want to discuss these types of contributions first. +At the moment, we are generally not looking to extend Zed's language or theme support by directly adding these features to Zed - we really want to build a plugin system to handle making the editor extensible going forward. + +If you are passionate about shipping new languages or themes we suggest contributing to the extension system to help us get there faster. ## Resources @@ -30,31 +32,26 @@ Zed is made up of several smaller crates - let's go over those you're most likel - [language](/crates/language) drives `editor`'s understanding of language - from providing a list of symbols to the syntax map. - [collab](/crates/collab) is the collaboration server itself, driving the collaboration features such as project sharing. - [rpc](/crates/rpc) defines messages to be exchanged with collaboration server. +- [theme](/crates/theme) defines the theme system and provides a default theme. +- [ui](/crates/ui) is a collection of UI components and common patterns used throughout Zed. -## Zed channels - -Once you have an idea of what you'd like to contribute, you'll want to communicate this to the team. If you're new to Zed's channels, here's a guide [link to up-to-date docs TODO](LINK) to help bring you up to speed. - -[Since ~February 2022, the Zed Industries team has been exclusively using Zed to build Zed](https://x.com/nathansobo/status/1497958891509932035). We've built these tools to specifically address our own issues and frustrations with the current state of collaborative coding. These are not features we've built to simply look flashy, we work in channels every day of the workweek, aggressively dogfooding everything. - -![Staff usage of channels](./assets/screenshots/staff_usage_of_channels.png) - -*Channel metrics were not collected prior to August 2023* +### Proposal & Discussion -While we still have improvements to make, we believe we've sanded down a lot of the sharp edges and that the experience is both smooth and enjoyable - one that gets you as close to hypothetically sitting next to your teammates as possible, even if you're potentially on different sides of the globe. We want to continue working this way amongst ourselves and we are extremely excited to work with *you* in this way. We invite you to contribute to Zed *through* Zed. +Before starting on a contribution, we ask that you look to see if there is any existing PRs, or in-Zed discussions about the thing you want to implement. If there is no existing work, find a public channel that is relevant to your contribution, check the channel notes to see which Zed team members typically work in that channel, and post a message in the chat. If you're not sure which channel is best, you can start a discussion, ask a team member or another contributor. -We plan to organize office hours on a weekly basis - they will take place in forelinked Zed channel. +*Please remember contributions not discussed with the team ahead of time likely have a lower chance of being merged or looked at in a timely manner.* -### Proposal & Discussion +## Implementation & Help -Before starting on a contribution, we ask that you look to see if there is any existing PRs, or in-Zed discussions about the thing you want to implement. If there is no existing work, find a [public channel TODO](LINK) that is relevant to your contribution, check the channel notes to see which Zed team members typically work in that channel, and post a message in the chat. If you're not sure which channel is best, you can post in the channel. +When you start working on your contribution if you find you are struggling with something specific feel free to reach out to the team for help. -*Please wait to begin working on your contribution until you've received feedback from the team. Turning down a contribution that was not discussed beforehand is a bummer for everyone.* +Remember the team is more likely to be available to help if you have already discussed your contribution or are working on something that is higher priority, like something on the roadmap or a top-ranking issue. -## Implementation & Help +We're happy to pair with you to help you learn the codebase and get your contribution merged. -Once approved, feel free to begin working on your contribution. If you have any questions, you can post in the channel you originally proposed your contribution in, or you can post in the channel. If you need help, reach out to a Zed teammate - we're happy to pair with you to help you learn the codebase and get your contribution merged. +**Zed makes heavy use of unit and integration testing, it is highly likely that contributions without any unit tests will be rejected** -**Zed makes heavy use of unit and integration testing, we encourage you to write tests for your contribution.** +Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming. +We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed. -Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming. We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed. +Remeber that smaller, incremental PRs are easier to review and merge than large PRs. From 9415f098d773f3292fbc8dcbef715d97fb5f2971 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 17 Jan 2024 13:09:02 -0500 Subject: [PATCH 259/334] Clean out old readme contents Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com> --- README.md | 109 +++++++----------------------------------------------- 1 file changed, 13 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index eed8dd4d91c249dfa4de57f47d79c6ad1e2749e9..c97f4f5134d82ee6f9aa84c72992233feb47e255 100644 --- a/README.md +++ b/README.md @@ -1,110 +1,27 @@ -# Zed - -[![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml) - -Welcome to Zed, a lightning-fast, collaborative code editor that makes your dreams come true. - -## Development tips - -### Dependencies - -* Install Xcode from https://apps.apple.com/us/app/xcode/id497799835?mt=12, and accept the license: - ``` - sudo xcodebuild -license - ``` - -* Install homebrew, node and rustup-init (rustup, rust, cargo, etc.) - ``` - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - brew install node rustup-init - rustup-init # follow the installation steps - ``` - -* Install postgres and configure the database - ``` - brew install postgresql@15 - brew services start postgresql@15 - psql -c "CREATE ROLE postgres SUPERUSER LOGIN" postgres - psql -U postgres -c "CREATE DATABASE zed" - ``` - -* Install the `LiveKit` server, the `PostgREST` API server, and the `foreman` process supervisor: - - ``` - brew install livekit - brew install postgrest - brew install foreman - ``` - -* Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: - - ``` - cd .. - git clone https://github.com/zed-industries/zed.dev - cd zed.dev && npm install - npm install -g vercel - ``` - -* Return to Zed project directory and Initialize submodules - - ``` - cd zed - git submodule update --init --recursive - ``` +# 🚧 TODO 🚧 -* Set up a local `zed` database and seed it with some initial users: +[ ] Add intro +[ ] Add link to contributing guide +[ ] Add barebones running zed from source instructions +[ ] Link out to further dev docs - [Create a personal GitHub token](https://github.com/settings/tokens/new) to run `script/bootstrap` once successfully: the token needs to have an access to private repositories for the script to work (`repo` OAuth scope). - Then delete that token. - - ``` - GITHUB_TOKEN=<$token> script/bootstrap - ``` - -* Now try running zed with collaboration disabled: - ``` - cargo run - ``` - -### Common errors - -* `xcrun: error: unable to find utility "metal", not a developer tool or in PATH` - * You need to install Xcode and then run: `xcode-select --switch /Applications/Xcode.app/Contents/Developer` - * (see https://github.com/gfx-rs/gfx/issues/2309) - -### Testing against locally-running servers - -Start the web and collab servers: - -``` -foreman start -``` +# Zed -If you want to run Zed pointed at the local servers, you can run: +[![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml) -``` -script/zed-local -``` +Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom]https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter). -### Dump element JSON +## Developing Zed -If you trigger `cmd-alt-i`, Zed will copy a JSON representation of the current window contents to the clipboard. You can paste this in a tool like [DJSON](https://chrome.google.com/webstore/detail/djson-json-viewer-formatt/chaeijjekipecdajnijdldjjipaegdjc?hl=en) to navigate the state of on-screen elements in a structured way. +- [Building Zed](./docs/src/developing_zed__building_zed.md) +- [Running Collaboration Locally](./docs/src/developing_zed__local_collaboration.md) ### Licensing +License information for third party dependencies must be correctly provided for CI to pass. + We use [`cargo-about`](https://github.com/EmbarkStudios/cargo-about) to automatically comply with open source licenses. If CI is failing, check the following: - Is it showing a `no license specified` error for a crate you've created? If so, add `publish = false` under `[package]` in your crate's Cargo.toml. - Is the error `failed to satisfy license requirements` for a dependency? If so, first determine what license the project has and whether this system is sufficient to comply with this license's requirements. If you're unsure, ask a lawyer. Once you've verified that this system is acceptable add the license's SPDX identifier to the `accepted` array in `script/licenses/zed-licenses.toml`. - Is `cargo-about` unable to find the license for a dependency? If so, add a clarification field at the end of `script/licenses/zed-licenses.toml`, as specified in the [cargo-about book](https://embarkstudios.github.io/cargo-about/cli/generate/config.html#crate-configuration). - - -### Wasm Plugins - -Zed has a Wasm-based plugin runtime which it currently uses to embed plugins. To compile Zed, you'll need to have the `wasm32-wasi` toolchain installed on your system. To install this toolchain, run: - -```bash -rustup target add wasm32-wasi -``` - -Plugins can be found in the `plugins` folder in the root. For more information about how plugins work, check the [Plugin Guide](./crates/plugin_runtime/README.md) in `crates/plugin_runtime/README.md`. From b64ae4df9c47507478360316ffd878e3151b2603 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 17 Jan 2024 13:09:13 -0500 Subject: [PATCH 260/334] Update developing zed doc Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com> --- docs/src/developing_zed__building_zed.md | 90 +++++++++++++++--------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index 7606e369d05cf02226ea5b783f4ff8369a661be3..947858033065619fd8f4bbca512af96bc7f6830e 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -7,56 +7,77 @@ How to build Zed from source for the first time. -## Prerequisites +### Prerequisites + +🚧 TODO 🚧 Update for open source - Be added to the GitHub organization - Be added to the Vercel team +- Create a [Personal Access Token](https://github.com/settings/personal-access-tokens/new) on Github + - 🚧 TODO 🚧 What permissions are required? + - 🚧 TODO 🚧 What changes when repo isn't private? + - Go to https://github.com/settings/tokens and Generate new token + - GitHub currently provides two kinds of tokens: + - Classic Tokens, where only `repo` (Full control of private repositories) OAuth scope has to be selected + Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories + - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos + - Keep the token in the browser tab/editor for the next two steps + +### Dependencies + +* Install [Rust](https://www.rust-lang.org/tools/install) + +* Install the [GitHub CLI](https://cli.github.com/), [Livekit] & [Foreman] + +```bash +brew install gh +brew install livekit +brew install foreman +``` + +* Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store + +* Install Xcode command line tools + +```bash +xcode-select --install +``` + +- If xcode-select --print-path prints /Library/Developer/CommandLineTools… run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.` -## Process +* Install [Postgres](https://postgresapp.com) -Expect this to take 30min to an hour! Some of these steps will take quite a while based on your connection speed, and how long your first build will be. +* Install the wasm toolchain + +```bash +rustup target add wasm32-wasi +``` + +### Building Zed from Source -1. Install the [GitHub CLI](https://cli.github.com/): - - `brew install gh` 1. Clone the `zed` repo - - `gh repo clone zed-industries/zed` -1. Install Xcode from the macOS App Store -1. Install Xcode command line tools - - `xcode-select --install` - - If xcode-select --print-path prints /Library/Developer/CommandLineTools… run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.` -1. Install [Postgres](https://postgresapp.com) -1. Install rust/rustup - - `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` -1. Install the wasm toolchain - - `rustup target add wasm32-wasi` -1. Install Livekit & Foreman - - `brew install livekit` - - `brew install foreman` -1. Generate an GitHub API Key - - Go to https://github.com/settings/tokens and Generate new token - - GitHub currently provides two kinds of tokens: - - Classic Tokens, where only `repo` (Full control of private repositories) OAuth scope has to be selected - Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories - - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - - Keep the token in the browser tab/editor for the next two steps -1. (Optional but reccomended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` -1. Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: + +```bash +gh repo clone zed-industries/zed +``` + +1. (Optional but recommended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` +1. (🚧 TODO 🚧 - Will this be relevant for open source?) Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: ``` cd .. git clone https://github.com/zed-industries/zed.dev cd zed.dev && npm install - npm install -g vercel + pnpm install -g vercel ``` -1. Link your zed.dev project to Vercel +1. (🚧 TODO 🚧 - Will this be relevant for open source?) Link your zed.dev project to Vercel - `vercel link` - Select the `zed-industries` team. If you don't have this get someone on the team to add you to it. - Select the `zed.dev` project -1. Run `vercel pull` to pull down the environment variables and project info from Vercel +1. (🚧 TODO 🚧 - Will this be relevant for open source?) Run `vercel pull` to pull down the environment variables and project info from Vercel 1. Open Postgres.app 1. From `./path/to/zed/`: - Run: - `GITHUB_TOKEN={yourGithubAPIToken} script/bootstrap` - - Replace `{yourGithubAPIToken}` with the API token you generated above. - You don't need to include the GITHUB_TOKEN if you exported it above. - Consider removing the token (if it's fine for you to recreate such tokens during occasional migrations) or store this token somewhere safe (like your Zed 1Password vault). - If you get: @@ -82,11 +103,12 @@ Expect this to take 30min to an hour! Some of these steps will take quite a whil ## Troubleshooting -### `error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)` +**`error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)`** - Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer` -### `xcrun: error: unable to find utility "metal", not a developer tool or in PATH` +**`xcrun: error: unable to find utility "metal", not a developer tool or in PATH`** + ### Seeding errors during `script/bootstrap` runs @@ -104,4 +126,4 @@ Same command ### If you experience errors that mention some dependency is using unstable features -Try `cargo clean` and `cargo build` +Try `cargo clean` and `cargo build`, From d0f22df1eb30d80b0117cca308b352bbf97444ef Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 17 Jan 2024 13:20:27 -0500 Subject: [PATCH 261/334] Reorganize building zed doc Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com> --- docs/src/developing_zed__building_zed.md | 100 ++++++++++++----------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index 947858033065619fd8f4bbca512af96bc7f6830e..a5270e2b2abc8f89e88e23d016e2e3a21a065efd 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -1,7 +1,7 @@ # Building Zed 🚧 TODO: -- [ ] Tidy up & update instructions + - [ ] Remove ZI-specific things - [ ] Rework any steps that currently require a ZI-specific account @@ -14,20 +14,20 @@ How to build Zed from source for the first time. - Be added to the GitHub organization - Be added to the Vercel team - Create a [Personal Access Token](https://github.com/settings/personal-access-tokens/new) on Github - - 🚧 TODO 🚧 What permissions are required? - - 🚧 TODO 🚧 What changes when repo isn't private? - - Go to https://github.com/settings/tokens and Generate new token - - GitHub currently provides two kinds of tokens: - - Classic Tokens, where only `repo` (Full control of private repositories) OAuth scope has to be selected - Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories - - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - - Keep the token in the browser tab/editor for the next two steps + - 🚧 TODO 🚧 What permissions are required? + - 🚧 TODO 🚧 What changes when repo isn't private? + - Go to https://github.com/settings/tokens and Generate new token + - GitHub currently provides two kinds of tokens: + - Classic Tokens, where only `repo` (Full control of private repositories) OAuth scope has to be selected + Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories + - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos + - Keep the token in the browser tab/editor for the next two steps ### Dependencies -* Install [Rust](https://www.rust-lang.org/tools/install) +- Install [Rust](https://www.rust-lang.org/tools/install) -* Install the [GitHub CLI](https://cli.github.com/), [Livekit] & [Foreman] +- Install the [GitHub CLI](https://cli.github.com/), [Livekit](https://formulae.brew.sh/formula/livekit) & [Foreman](https://formulae.brew.sh/formula/foreman) ```bash brew install gh @@ -35,15 +35,15 @@ brew install livekit brew install foreman ``` -* Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store +- Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store -* Install Xcode command line tools +- Install [Xcode command line tools](https://developer.apple.com/xcode/resources/) ```bash xcode-select --install ``` -- If xcode-select --print-path prints /Library/Developer/CommandLineTools… run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.` +- If `xcode-select --print-path prints /Library/Developer/CommandLineTools…` run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.` * Install [Postgres](https://postgresapp.com) @@ -63,43 +63,34 @@ gh repo clone zed-industries/zed 1. (Optional but recommended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` 1. (🚧 TODO 🚧 - Will this be relevant for open source?) Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: - ``` - cd .. - git clone https://github.com/zed-industries/zed.dev - cd zed.dev && npm install - pnpm install -g vercel - ``` + +```bash +cd .. +git clone https://github.com/zed-industries/zed.dev +cd zed.dev && npm install +pnpm install -g vercel +``` + 1. (🚧 TODO 🚧 - Will this be relevant for open source?) Link your zed.dev project to Vercel - - `vercel link` - - Select the `zed-industries` team. If you don't have this get someone on the team to add you to it. - - Select the `zed.dev` project + +- `vercel link` +- Select the `zed-industries` team. If you don't have this get someone on the team to add you to it. +- Select the `zed.dev` project + 1. (🚧 TODO 🚧 - Will this be relevant for open source?) Run `vercel pull` to pull down the environment variables and project info from Vercel 1. Open Postgres.app -1. From `./path/to/zed/`: - - Run: - - `GITHUB_TOKEN={yourGithubAPIToken} script/bootstrap` - - You don't need to include the GITHUB_TOKEN if you exported it above. - - Consider removing the token (if it's fine for you to recreate such tokens during occasional migrations) or store this token somewhere safe (like your Zed 1Password vault). - - If you get: - - ```bash - Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)! - Please create a new installation in /opt/homebrew using one of the - "Alternative Installs" from: - https://docs.brew.sh/Installation - ``` - - In that case try: - - `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` - - If Homebrew is not in your PATH: - - Replace `{username}` with your home folder name (usually your login name) - - `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/{username}/.zprofile` - - `eval "$(/opt/homebrew/bin/brew shellenv)"` +1. From `./path/to/zed/` run `GITHUB_TOKEN={yourGithubAPIToken} script/bootstrap` + +- You don't need to include the GITHUB_TOKEN if you exported it above. +- Consider removing the token (if it's fine for you to recreate such tokens during occasional migrations) or store this token somewhere safe (like your Zed 1Password vault). + 1. To run the Zed app: - - If you are working on zed: - - `cargo run` - - If you are just using the latest version, but not working on zed: - - `cargo run --release` - - If you need to run the collaboration server locally: - - `script/zed-local` + - If you are working on zed: + - `cargo run` + - If you are just using the latest version, but not working on zed: + - `cargo run --release` + - If you need to run the collaboration server locally: + - `script/zed-local` ## Troubleshooting @@ -109,8 +100,21 @@ gh repo clone zed-industries/zed **`xcrun: error: unable to find utility "metal", not a developer tool or in PATH`** +### `script/bootstrap` + +```bash +Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)! +Please create a new installation in /opt/homebrew using one of the +"Alternative Installs" from: +https://docs.brew.sh/Installation +``` + +- In that case try `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` -### Seeding errors during `script/bootstrap` runs +- If Homebrew is not in your PATH: + - Replace `{username}` with your home folder name (usually your login name) + - `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/{username}/.zprofile` + - `eval "$(/opt/homebrew/bin/brew shellenv)"` ``` seeding database... From ed67363ea38e06f8fea4fd761439510fe31c5fff Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 17 Jan 2024 13:24:05 -0500 Subject: [PATCH 262/334] Update README.md FIx typos --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c97f4f5134d82ee6f9aa84c72992233feb47e255..737615623194a958f4d8b8d129dfab50475e2e8b 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # 🚧 TODO 🚧 -[ ] Add intro -[ ] Add link to contributing guide -[ ] Add barebones running zed from source instructions -[ ] Link out to further dev docs +- [ ] Add intro +- [ ] Add link to contributing guide +- [ ] Add barebones running zed from source instructions +- [ ] Link out to further dev docs # Zed [![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml) -Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom]https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter). +Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter). ## Developing Zed From 4e4a1e0dd1196b814667bf235f4b931d8b936e0e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Jan 2024 13:32:38 -0500 Subject: [PATCH 263/334] Document the public interface of the `vim` crate (#4093) This PR documents the public interface of the `vim` crate. Release Notes: - N/A --------- Co-authored-by: Conrad --- crates/vim/src/mode_indicator.rs | 11 +++-------- crates/vim/src/vim.rs | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/crates/vim/src/mode_indicator.rs b/crates/vim/src/mode_indicator.rs index 4cd8e6690092d0f5f18528373e14d91f2311b156..b669b16112874a89cd303499cc0d17eab6b99267 100644 --- a/crates/vim/src/mode_indicator.rs +++ b/crates/vim/src/mode_indicator.rs @@ -4,12 +4,14 @@ use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView}; use crate::{state::Mode, Vim}; +/// The ModeIndicator displays the current mode in the status bar. pub struct ModeIndicator { - pub mode: Option, + pub(crate) mode: Option, _subscriptions: Vec, } impl ModeIndicator { + /// Construct a new mode indicator in this window. pub fn new(cx: &mut ViewContext) -> Self { let _subscriptions = vec![ cx.observe_global::(|this, cx| this.update_mode(cx)), @@ -37,13 +39,6 @@ impl ModeIndicator { self.mode = None; } } - - pub fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext) { - if self.mode != Some(mode) { - self.mode = Some(mode); - cx.notify(); - } - } } impl Render for ModeIndicator { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 3579bf36fe29a7276496a1bbefd95dabf6cc8bc2..e03efb0a64b4b55e92ed8d92d8837351ac573e0d 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -1,3 +1,5 @@ +//! Vim support for Zed. + #[cfg(test)] mod test; @@ -38,12 +40,18 @@ use crate::state::ReplayableAction; /// Default: false pub struct VimModeSetting(pub bool); +/// An Action to Switch between modes #[derive(Clone, Deserialize, PartialEq)] pub struct SwitchMode(pub Mode); +/// PushOperator is used to put vim into a "minor" mode, +/// where it's waiting for a specific next set of keystrokes. +/// For example 'd' needs a motion to complete. #[derive(Clone, Deserialize, PartialEq)] pub struct PushOperator(pub Operator); +/// Number is used to manage vim's count. Pushing a digit +/// multiplis the current value by 10 and adds the digit. #[derive(Clone, Deserialize, PartialEq)] struct Number(usize); @@ -51,11 +59,13 @@ actions!( vim, [Tab, Enter, Object, InnerObject, FindForward, FindBackward] ); + // in the workspace namespace so it's not filtered out when vim is disabled. actions!(workspace, [ToggleVimMode]); impl_actions!(vim, [SwitchMode, PushOperator, Number]); +/// Initializes the `vim` crate. pub fn init(cx: &mut AppContext) { cx.set_global(Vim::default()); VimModeSetting::register(cx); @@ -119,6 +129,7 @@ fn register(workspace: &mut Workspace, cx: &mut ViewContext) { visual::register(workspace, cx); } +/// Registers a keystroke observer to observe keystrokes for the Vim integration. pub fn observe_keystrokes(cx: &mut WindowContext) { cx.observe_keystrokes(|keystroke_event, cx| { if let Some(action) = keystroke_event @@ -160,6 +171,7 @@ pub fn observe_keystrokes(cx: &mut WindowContext) { .detach() } +/// The state pertaining to Vim mode. Stored as a global. #[derive(Default)] pub struct Vim { active_editor: Option>, @@ -251,6 +263,8 @@ impl Vim { Some(editor.update(cx, update)) } + /// When doing an action that modifies the buffer, we start recording so that `.` + /// will replay the action. pub fn start_recording(&mut self, cx: &mut WindowContext) { if !self.workspace_state.replaying { self.workspace_state.recording = true; @@ -295,12 +309,19 @@ impl Vim { } } + /// When finishing an action that modifies the buffer, stop recording. + /// as you usually call this within a keystroke handler we also ensure that + /// the current action is recorded. pub fn stop_recording(&mut self) { if self.workspace_state.recording { self.workspace_state.stop_recording_after_next_action = true; } } + /// Stops recording actions immediately rather than waiting until after the + /// next action to stop recording. + /// + /// This doesn't include the current action. pub fn stop_recording_immediately(&mut self, action: Box) { if self.workspace_state.recording { self.workspace_state @@ -311,6 +332,7 @@ impl Vim { } } + /// Explicitly record one action (equiavlent to start_recording and stop_recording) pub fn record_current_action(&mut self, cx: &mut WindowContext) { self.start_recording(cx); self.stop_recording(); @@ -516,6 +538,7 @@ impl Vim { } } + /// Returns the state of the active editor. pub fn state(&self) -> &EditorState { if let Some(active_editor) = self.active_editor.as_ref() { if let Some(state) = self.editor_states.get(&active_editor.entity_id()) { @@ -526,6 +549,7 @@ impl Vim { &self.default_state } + /// Updates the state of the active editor. pub fn update_state(&mut self, func: impl FnOnce(&mut EditorState) -> T) -> T { let mut state = self.state().clone(); let ret = func(&mut state); From 6734e528a845300c94675431c8b1d0d4d9728c5c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 10:33:32 -0800 Subject: [PATCH 264/334] Revert "Bump livekit client" This reverts commit 5730d0ef2107f2a10fe01595ce34f14f97e289ad. --- crates/live_kit_client/LiveKitBridge/Package.resolved | 8 ++++---- crates/live_kit_client/LiveKitBridge/Package.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved index bf17ef24c57da952f27caee6c995af0800a73e2e..b925bc8f0d5ef290993fa0d49adcf221dd3570f6 100644 --- a/crates/live_kit_client/LiveKitBridge/Package.resolved +++ b/crates/live_kit_client/LiveKitBridge/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/livekit/client-sdk-swift.git", "state": { "branch": null, - "revision": "8b9cefed8d1669ec8fce41376b56dce3036a5f50", - "version": "1.1.4" + "revision": "7331b813a5ab8a95cfb81fb2b4ed10519428b9ff", + "version": "1.0.12" } }, { @@ -24,8 +24,8 @@ "repositoryURL": "https://github.com/webrtc-sdk/Specs.git", "state": { "branch": null, - "revision": "4fa8d6d647fc759cdd0265fd413d2f28ea2e0e08", - "version": "114.5735.8" + "revision": "2f6bab30c8df0fe59ab3e58bc99097f757f85f65", + "version": "104.5112.17" } }, { diff --git a/crates/live_kit_client/LiveKitBridge/Package.swift b/crates/live_kit_client/LiveKitBridge/Package.swift index abb38efca6a734c935ecdd7438570a3fa6f94230..d7b5c271b95496112b2fc368a851506d54b176b8 100644 --- a/crates/live_kit_client/LiveKitBridge/Package.swift +++ b/crates/live_kit_client/LiveKitBridge/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["LiveKitBridge"]), ], dependencies: [ - .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.1.4")), + .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.0.12")), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. From cf5dc099fb9282946372f067795ae0061b36c416 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Jan 2024 13:38:12 -0500 Subject: [PATCH 265/334] Add more documentation to `collab` (#4095) This PR adds more documentation to the `collab` crate. Release Notes: - N/A --------- Co-authored-by: Conrad --- crates/collab/src/auth.rs | 7 +++ crates/collab/src/db.rs | 26 ++++++++ crates/collab/src/db/ids.rs | 20 ++++++ crates/collab/src/db/queries/access_tokens.rs | 2 + crates/collab/src/db/queries/buffers.rs | 10 ++- crates/collab/src/db/queries/channels.rs | 28 +++++++-- crates/collab/src/db/queries/contacts.rs | 8 +++ crates/collab/src/db/queries/messages.rs | 11 ++++ crates/collab/src/db/queries/notifications.rs | 4 ++ crates/collab/src/db/queries/projects.rs | 18 ++++++ crates/collab/src/db/queries/rooms.rs | 6 ++ crates/collab/src/db/queries/servers.rs | 6 ++ crates/collab/src/db/queries/users.rs | 15 +++++ crates/collab/src/db/tables/user.rs | 1 + crates/collab/src/rpc.rs | 62 +++++++++++++++++++ 15 files changed, 219 insertions(+), 5 deletions(-) diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index 9ce602c5778c25efe89017b58b3498108de38038..df3ded28e4a2f1f86ac1421e91dc6be31f7f8408 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -27,6 +27,8 @@ lazy_static! { .unwrap(); } +/// Validates the authorization header. This has two mechanisms, one for the ADMIN_TOKEN +/// and one for the access tokens that we issue. pub async fn validate_header(mut req: Request, next: Next) -> impl IntoResponse { let mut auth_header = req .headers() @@ -88,6 +90,8 @@ struct AccessTokenJson { token: String, } +/// Creates a new access token to identify the given user. before returning it, you should +/// encrypt it with the user's public key. pub async fn create_access_token(db: &db::Database, user_id: UserId) -> Result { const VERSION: usize = 1; let access_token = rpc::auth::random_token(); @@ -122,6 +126,8 @@ fn hash_access_token(token: &str) -> Result { .to_string()) } +/// Encrypts the given access token with the given public key to avoid leaking it on the way +/// to the client. pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result { let native_app_public_key = rpc::auth::PublicKey::try_from(public_key).context("failed to parse app public key")?; @@ -131,6 +137,7 @@ pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result) -> Result { let token: AccessTokenJson = serde_json::from_str(&token)?; diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index df33416a46b00cea970e05f22b48c08cc02230cf..480dcf6f8595143659069739104608dac4c15fd8 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -47,6 +47,8 @@ pub use ids::*; pub use sea_orm::ConnectOptions; pub use tables::user::Model as User; +/// Database gives you a handle that lets you access the database. +/// It handles pooling internally. pub struct Database { options: ConnectOptions, pool: DatabaseConnection, @@ -62,6 +64,7 @@ pub struct Database { // The `Database` type has so many methods that its impl blocks are split into // separate files in the `queries` folder. impl Database { + /// Connects to the database with the given options pub async fn new(options: ConnectOptions, executor: Executor) -> Result { sqlx::any::install_default_drivers(); Ok(Self { @@ -82,6 +85,7 @@ impl Database { self.rooms.clear(); } + /// Runs the database migrations. pub async fn migrate( &self, migrations_path: &Path, @@ -123,11 +127,15 @@ impl Database { Ok(new_migrations) } + /// Initializes static data that resides in the database by upserting it. pub async fn initialize_static_data(&mut self) -> Result<()> { self.initialize_notification_kinds().await?; Ok(()) } + /// Transaction runs things in a transaction. If you want to call other methods + /// and pass the transaction around you need to reborrow the transaction at each + /// call site with: `&*tx`. pub async fn transaction(&self, f: F) -> Result where F: Send + Fn(TransactionHandle) -> Fut, @@ -160,6 +168,7 @@ impl Database { self.run(body).await } + /// The same as room_transaction, but if you need to only optionally return a Room. async fn optional_room_transaction(&self, f: F) -> Result>> where F: Send + Fn(TransactionHandle) -> Fut, @@ -210,6 +219,9 @@ impl Database { self.run(body).await } + /// room_transaction runs the block in a transaction. It returns a RoomGuard, that keeps + /// the database locked until it is dropped. This ensures that updates sent to clients are + /// properly serialized with respect to database changes. async fn room_transaction(&self, room_id: RoomId, f: F) -> Result> where F: Send + Fn(TransactionHandle) -> Fut, @@ -330,6 +342,7 @@ fn is_serialization_error(error: &Error) -> bool { } } +/// A handle to a [`DatabaseTransaction`]. pub struct TransactionHandle(Arc>); impl Deref for TransactionHandle { @@ -340,6 +353,8 @@ impl Deref for TransactionHandle { } } +/// [`RoomGuard`] keeps a database transaction alive until it is dropped. +/// so that updates to rooms are serialized. pub struct RoomGuard { data: T, _guard: OwnedMutexGuard<()>, @@ -361,6 +376,7 @@ impl DerefMut for RoomGuard { } impl RoomGuard { + /// Returns the inner value of the guard. pub fn into_inner(self) -> T { self.data } @@ -420,12 +436,14 @@ pub struct WaitlistSummary { pub unknown_count: i64, } +/// The parameters to create a new user. #[derive(Debug, Serialize, Deserialize)] pub struct NewUserParams { pub github_login: String, pub github_user_id: i32, } +/// The result of creating a new user. #[derive(Debug)] pub struct NewUserResult { pub user_id: UserId, @@ -434,6 +452,7 @@ pub struct NewUserResult { pub signup_device_id: Option, } +/// The result of moving a channel. #[derive(Debug)] pub struct MoveChannelResult { pub participants_to_update: HashMap, @@ -441,18 +460,21 @@ pub struct MoveChannelResult { pub moved_channels: HashSet, } +/// The result of renaming a channel. #[derive(Debug)] pub struct RenameChannelResult { pub channel: Channel, pub participants_to_update: HashMap, } +/// The result of creating a channel. #[derive(Debug)] pub struct CreateChannelResult { pub channel: Channel, pub participants_to_update: Vec<(UserId, ChannelsForUser)>, } +/// The result of setting a channel's visibility. #[derive(Debug)] pub struct SetChannelVisibilityResult { pub participants_to_update: HashMap, @@ -460,6 +482,7 @@ pub struct SetChannelVisibilityResult { pub channels_to_remove: Vec, } +/// The result of updating a channel membership. #[derive(Debug)] pub struct MembershipUpdated { pub channel_id: ChannelId, @@ -467,12 +490,14 @@ pub struct MembershipUpdated { pub removed_channels: Vec, } +/// The result of setting a member's role. #[derive(Debug)] pub enum SetMemberRoleResult { InviteUpdated(Channel), MembershipUpdated(MembershipUpdated), } +/// The result of inviting a member to a channel. #[derive(Debug)] pub struct InviteMemberResult { pub channel: Channel, @@ -497,6 +522,7 @@ pub struct Channel { pub name: String, pub visibility: ChannelVisibility, pub role: ChannelRole, + /// parent_path is the channel ids from the root to this one (not including this one) pub parent_path: Vec, } diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 9dbbfaff8f8d0e94ea56b02eda37485ddc695fa8..a920265b5703e55ef482a88c03facea95194265b 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -19,19 +19,23 @@ macro_rules! id_type { Deserialize, DeriveValueType, )] + #[allow(missing_docs)] #[serde(transparent)] pub struct $name(pub i32); impl $name { #[allow(unused)] + #[allow(missing_docs)] pub const MAX: Self = Self(i32::MAX); #[allow(unused)] + #[allow(missing_docs)] pub fn from_proto(value: u64) -> Self { Self(value as i32) } #[allow(unused)] + #[allow(missing_docs)] pub fn to_proto(self) -> u64 { self.0 as u64 } @@ -84,21 +88,28 @@ id_type!(FlagId); id_type!(NotificationId); id_type!(NotificationKindId); +/// ChannelRole gives you permissions for both channels and calls. #[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum ChannelRole { + /// Admin can read/write and change permissions. #[sea_orm(string_value = "admin")] Admin, + /// Member can read/write, but not change pemissions. #[sea_orm(string_value = "member")] #[default] Member, + /// Guest can read, but not write. + /// (thought they can use the channel chat) #[sea_orm(string_value = "guest")] Guest, + /// Banned may not read. #[sea_orm(string_value = "banned")] Banned, } impl ChannelRole { + /// Returns true if this role is more powerful than the other role. pub fn should_override(&self, other: Self) -> bool { use ChannelRole::*; match self { @@ -109,6 +120,7 @@ impl ChannelRole { } } + /// Returns the maximal role between the two pub fn max(&self, other: Self) -> Self { if self.should_override(other) { *self @@ -117,6 +129,7 @@ impl ChannelRole { } } + /// True if the role allows access to all descendant channels pub fn can_see_all_descendants(&self) -> bool { use ChannelRole::*; match self { @@ -125,6 +138,7 @@ impl ChannelRole { } } + /// True if the role only allows access to public descendant channels pub fn can_only_see_public_descendants(&self) -> bool { use ChannelRole::*; match self { @@ -133,6 +147,7 @@ impl ChannelRole { } } + /// True if the role can share screen/microphone/projects into rooms. pub fn can_publish_to_rooms(&self) -> bool { use ChannelRole::*; match self { @@ -141,6 +156,7 @@ impl ChannelRole { } } + /// True if the role can edit shared projects. pub fn can_edit_projects(&self) -> bool { use ChannelRole::*; match self { @@ -149,6 +165,7 @@ impl ChannelRole { } } + /// True if the role can read shared projects. pub fn can_read_projects(&self) -> bool { use ChannelRole::*; match self { @@ -187,11 +204,14 @@ impl Into for ChannelRole { } } +/// ChannelVisibility controls whether channels are public or private. #[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum ChannelVisibility { + /// Public channels are visible to anyone with the link. People join with the Guest role by default. #[sea_orm(string_value = "public")] Public, + /// Members channels are only visible to members of this channel or its parents. #[sea_orm(string_value = "members")] #[default] Members, diff --git a/crates/collab/src/db/queries/access_tokens.rs b/crates/collab/src/db/queries/access_tokens.rs index 589b6483dfceb5df285ac67b03edbee493e4705b..e0338189708c41d63b168f37c89e2c62fe3521b9 100644 --- a/crates/collab/src/db/queries/access_tokens.rs +++ b/crates/collab/src/db/queries/access_tokens.rs @@ -2,6 +2,7 @@ use super::*; use sea_orm::sea_query::Query; impl Database { + /// Creates a new access token for the given user. pub async fn create_access_token( &self, user_id: UserId, @@ -39,6 +40,7 @@ impl Database { .await } + /// Retrieves the access token with the given ID. pub async fn get_access_token( &self, access_token_id: AccessTokenId, diff --git a/crates/collab/src/db/queries/buffers.rs b/crates/collab/src/db/queries/buffers.rs index 9eddb1f6187a80f4f88f8d13e9aff3f4c310941f..85e71d753b972db0515954406819f8c2fc48f9a9 100644 --- a/crates/collab/src/db/queries/buffers.rs +++ b/crates/collab/src/db/queries/buffers.rs @@ -9,6 +9,8 @@ pub struct LeftChannelBuffer { } impl Database { + /// Open a channel buffer. Returns the current contents, and adds you to the list of people + /// to notify on changes. pub async fn join_channel_buffer( &self, channel_id: ChannelId, @@ -121,6 +123,7 @@ impl Database { .await } + /// Rejoin a channel buffer (after a connection interruption) pub async fn rejoin_channel_buffers( &self, buffers: &[proto::ChannelBufferVersion], @@ -232,6 +235,7 @@ impl Database { .await } + /// Clear out any buffer collaborators who are no longer collaborating. pub async fn clear_stale_channel_buffer_collaborators( &self, channel_id: ChannelId, @@ -274,6 +278,7 @@ impl Database { .await } + /// Close the channel buffer, and stop receiving updates for it. pub async fn leave_channel_buffer( &self, channel_id: ChannelId, @@ -286,6 +291,7 @@ impl Database { .await } + /// Close the channel buffer, and stop receiving updates for it. pub async fn channel_buffer_connection_lost( &self, connection: ConnectionId, @@ -309,6 +315,7 @@ impl Database { Ok(()) } + /// Close all open channel buffers pub async fn leave_channel_buffers( &self, connection: ConnectionId, @@ -342,7 +349,7 @@ impl Database { .await } - pub async fn leave_channel_buffer_internal( + async fn leave_channel_buffer_internal( &self, channel_id: ChannelId, connection: ConnectionId, @@ -798,6 +805,7 @@ impl Database { Ok(changes) } + /// Returns the latest operations for the buffers with the specified IDs. pub async fn get_latest_operations_for_buffers( &self, buffer_ids: impl IntoIterator, diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 6243b03bf7ad2f331b3ede34c2390db574940546..7ff9f00bc119c12a17987b2b7dff24e354286c33 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -40,6 +40,7 @@ impl Database { .id) } + /// Creates a new channel. pub async fn create_channel( &self, name: &str, @@ -97,6 +98,7 @@ impl Database { .await } + /// Adds a user to the specified channel. pub async fn join_channel( &self, channel_id: ChannelId, @@ -179,6 +181,7 @@ impl Database { .await } + /// Sets the visibiltity of the given channel. pub async fn set_channel_visibility( &self, channel_id: ChannelId, @@ -258,6 +261,7 @@ impl Database { .await } + /// Deletes the channel with the specified ID. pub async fn delete_channel( &self, channel_id: ChannelId, @@ -294,6 +298,7 @@ impl Database { .await } + /// Invites a user to a channel as a member. pub async fn invite_channel_member( &self, channel_id: ChannelId, @@ -349,6 +354,7 @@ impl Database { Ok(new_name) } + /// Renames the specified channel. pub async fn rename_channel( &self, channel_id: ChannelId, @@ -387,6 +393,7 @@ impl Database { .await } + /// accept or decline an invite to join a channel pub async fn respond_to_channel_invite( &self, channel_id: ChannelId, @@ -486,6 +493,7 @@ impl Database { }) } + /// Removes a channel member. pub async fn remove_channel_member( &self, channel_id: ChannelId, @@ -530,6 +538,7 @@ impl Database { .await } + /// Returns all channel invites for the user with the given ID. pub async fn get_channel_invites_for_user(&self, user_id: UserId) -> Result> { self.transaction(|tx| async move { let mut role_for_channel: HashMap = HashMap::default(); @@ -565,6 +574,7 @@ impl Database { .await } + /// Returns all channels for the user with the given ID. pub async fn get_channels_for_user(&self, user_id: UserId) -> Result { self.transaction(|tx| async move { let tx = tx; @@ -574,6 +584,8 @@ impl Database { .await } + /// Returns all channels for the user with the given ID that are descendants + /// of the specified ancestor channel. pub async fn get_user_channels( &self, user_id: UserId, @@ -743,6 +755,7 @@ impl Database { Ok(results) } + /// Sets the role for the specified channel member. pub async fn set_channel_member_role( &self, channel_id: ChannelId, @@ -786,6 +799,7 @@ impl Database { .await } + /// Returns the details for the specified channel member. pub async fn get_channel_participant_details( &self, channel_id: ChannelId, @@ -911,6 +925,7 @@ impl Database { .collect()) } + /// Returns the participants in the given channel. pub async fn get_channel_participants( &self, channel: &channel::Model, @@ -925,6 +940,7 @@ impl Database { .collect()) } + /// Returns whether the given user is an admin in the specified channel. pub async fn check_user_is_channel_admin( &self, channel: &channel::Model, @@ -943,6 +959,7 @@ impl Database { } } + /// Returns whether the given user is a member of the specified channel. pub async fn check_user_is_channel_member( &self, channel: &channel::Model, @@ -958,6 +975,7 @@ impl Database { } } + /// Returns whether the given user is a participant in the specified channel. pub async fn check_user_is_channel_participant( &self, channel: &channel::Model, @@ -975,6 +993,7 @@ impl Database { } } + /// Returns a user's pending invite for the given channel, if one exists. pub async fn pending_invite_for_channel( &self, channel: &channel::Model, @@ -991,7 +1010,7 @@ impl Database { Ok(row) } - pub async fn public_parent_channel( + async fn public_parent_channel( &self, channel: &channel::Model, tx: &DatabaseTransaction, @@ -1003,7 +1022,7 @@ impl Database { Ok(path.pop()) } - pub async fn public_ancestors_including_self( + pub(crate) async fn public_ancestors_including_self( &self, channel: &channel::Model, tx: &DatabaseTransaction, @@ -1018,6 +1037,7 @@ impl Database { Ok(visible_channels) } + /// Returns the role for a user in the given channel. pub async fn channel_role_for_user( &self, channel: &channel::Model, @@ -1143,7 +1163,7 @@ impl Database { .await?) } - /// Returns the channel with the given ID + /// Returns the channel with the given ID. pub async fn get_channel(&self, channel_id: ChannelId, user_id: UserId) -> Result { self.transaction(|tx| async move { let channel = self.get_channel_internal(channel_id, &*tx).await?; @@ -1156,7 +1176,7 @@ impl Database { .await } - pub async fn get_channel_internal( + pub(crate) async fn get_channel_internal( &self, channel_id: ChannelId, tx: &DatabaseTransaction, diff --git a/crates/collab/src/db/queries/contacts.rs b/crates/collab/src/db/queries/contacts.rs index f31f1addbd2b4a210abfa9810f062585a7b656e4..c66c33b80dfd795c79f8ef002b0ee122954232cb 100644 --- a/crates/collab/src/db/queries/contacts.rs +++ b/crates/collab/src/db/queries/contacts.rs @@ -1,6 +1,7 @@ use super::*; impl Database { + /// Retrieves the contacts for the user with the given ID. pub async fn get_contacts(&self, user_id: UserId) -> Result> { #[derive(Debug, FromQueryResult)] struct ContactWithUserBusyStatuses { @@ -86,6 +87,7 @@ impl Database { .await } + /// Returns whether the given user is a busy (on a call). pub async fn is_user_busy(&self, user_id: UserId) -> Result { self.transaction(|tx| async move { let participant = room_participant::Entity::find() @@ -97,6 +99,9 @@ impl Database { .await } + /// Returns whether the user with `user_id_1` has the user with `user_id_2` as a contact. + /// + /// In order for this to return `true`, `user_id_2` must have an accepted invite from `user_id_1`. pub async fn has_contact(&self, user_id_1: UserId, user_id_2: UserId) -> Result { self.transaction(|tx| async move { let (id_a, id_b) = if user_id_1 < user_id_2 { @@ -119,6 +124,7 @@ impl Database { .await } + /// Invite the user with `receiver_id` to be a contact of the user with `sender_id`. pub async fn send_contact_request( &self, sender_id: UserId, @@ -231,6 +237,7 @@ impl Database { .await } + /// Dismisses a contact notification for the given user. pub async fn dismiss_contact_notification( &self, user_id: UserId, @@ -272,6 +279,7 @@ impl Database { .await } + /// Accept or decline a contact request pub async fn respond_to_contact_request( &self, responder_id: UserId, diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index 96942c052df75c9406272da3a563533342f64406..9ee313d91b4f916c69789b2048c1867be207f676 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -4,6 +4,7 @@ use sea_orm::TryInsertResult; use time::OffsetDateTime; impl Database { + /// Inserts a record representing a user joining the chat for a given channel. pub async fn join_channel_chat( &self, channel_id: ChannelId, @@ -28,6 +29,7 @@ impl Database { .await } + /// Removes `channel_chat_participant` records associated with the given connection ID. pub async fn channel_chat_connection_lost( &self, connection_id: ConnectionId, @@ -47,6 +49,8 @@ impl Database { Ok(()) } + /// Removes `channel_chat_participant` records associated with the given user ID so they + /// will no longer get chat notifications. pub async fn leave_channel_chat( &self, channel_id: ChannelId, @@ -72,6 +76,9 @@ impl Database { .await } + /// Retrieves the messages in the specified channel. + /// + /// Use `before_message_id` to paginate through the channel's messages. pub async fn get_channel_messages( &self, channel_id: ChannelId, @@ -103,6 +110,7 @@ impl Database { .await } + /// Returns the channel messages with the given IDs. pub async fn get_channel_messages_by_id( &self, user_id: UserId, @@ -190,6 +198,7 @@ impl Database { Ok(messages) } + /// Creates a new channel message. pub async fn create_channel_message( &self, channel_id: ChannelId, @@ -376,6 +385,7 @@ impl Database { Ok(()) } + /// Returns the unseen messages for the given user in the specified channels. pub async fn unseen_channel_messages( &self, user_id: UserId, @@ -449,6 +459,7 @@ impl Database { Ok(changes) } + /// Removes the channel message with the given ID. pub async fn remove_channel_message( &self, channel_id: ChannelId, diff --git a/crates/collab/src/db/queries/notifications.rs b/crates/collab/src/db/queries/notifications.rs index 6f2511c23e7cd383760aa29ec62a65ca30c636d8..57685e141b78e5aa6c4a541c385bf056b1802000 100644 --- a/crates/collab/src/db/queries/notifications.rs +++ b/crates/collab/src/db/queries/notifications.rs @@ -2,6 +2,7 @@ use super::*; use rpc::Notification; impl Database { + /// Initializes the different kinds of notifications by upserting records for them. pub async fn initialize_notification_kinds(&mut self) -> Result<()> { notification_kind::Entity::insert_many(Notification::all_variant_names().iter().map( |kind| notification_kind::ActiveModel { @@ -28,6 +29,7 @@ impl Database { Ok(()) } + /// Returns the notifications for the given recipient. pub async fn get_notifications( &self, recipient_id: UserId, @@ -140,6 +142,7 @@ impl Database { .await } + /// Marks the given notification as read. pub async fn mark_notification_as_read( &self, recipient_id: UserId, @@ -150,6 +153,7 @@ impl Database { .await } + /// Marks the notification with the given ID as read. pub async fn mark_notification_as_read_by_id( &self, recipient_id: UserId, diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index d82a597038d063405a22aeddd0623d7f9bfc9eb0..f81403a796b0e2402eef5c46124c8b51bc68d2b5 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -1,6 +1,7 @@ use super::*; impl Database { + /// Returns the count of all projects, excluding ones marked as admin. pub async fn project_count_excluding_admins(&self) -> Result { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryAs { @@ -21,6 +22,7 @@ impl Database { .await } + /// Shares a project with the given room. pub async fn share_project( &self, room_id: RoomId, @@ -100,6 +102,7 @@ impl Database { .await } + /// Unshares the given project. pub async fn unshare_project( &self, project_id: ProjectId, @@ -126,6 +129,7 @@ impl Database { .await } + /// Updates the worktrees associated with the given project. pub async fn update_project( &self, project_id: ProjectId, @@ -346,6 +350,7 @@ impl Database { .await } + /// Updates the diagnostic summary for the given connection. pub async fn update_diagnostic_summary( &self, update: &proto::UpdateDiagnosticSummary, @@ -401,6 +406,7 @@ impl Database { .await } + /// Starts the language server for the given connection. pub async fn start_language_server( &self, update: &proto::StartLanguageServer, @@ -447,6 +453,7 @@ impl Database { .await } + /// Updates the worktree settings for the given connection. pub async fn update_worktree_settings( &self, update: &proto::UpdateWorktreeSettings, @@ -499,6 +506,7 @@ impl Database { .await } + /// Adds the given connection to the specified project. pub async fn join_project( &self, project_id: ProjectId, @@ -704,6 +712,7 @@ impl Database { .await } + /// Removes the given connection from the specified project. pub async fn leave_project( &self, project_id: ProjectId, @@ -805,6 +814,7 @@ impl Database { .map(|guard| guard.into_inner()) } + /// Returns the host connection for a read-only request to join a shared project. pub async fn host_for_read_only_project_request( &self, project_id: ProjectId, @@ -842,6 +852,7 @@ impl Database { .map(|guard| guard.into_inner()) } + /// Returns the host connection for a request to join a shared project. pub async fn host_for_mutating_project_request( &self, project_id: ProjectId, @@ -927,6 +938,10 @@ impl Database { .await } + /// Returns the connection IDs in the given project. + /// + /// The provided `connection_id` must also be a collaborator in the project, + /// otherwise an error will be returned. pub async fn project_connection_ids( &self, project_id: ProjectId, @@ -976,6 +991,7 @@ impl Database { Ok(guest_connection_ids) } + /// Returns the [`RoomId`] for the given project. pub async fn room_id_for_project(&self, project_id: ProjectId) -> Result { self.transaction(|tx| async move { let project = project::Entity::find_by_id(project_id) @@ -1020,6 +1036,7 @@ impl Database { .await } + /// Adds the given follower connection as a follower of the given leader connection. pub async fn follow( &self, room_id: RoomId, @@ -1050,6 +1067,7 @@ impl Database { .await } + /// Removes the given follower connection as a follower of the given leader connection. pub async fn unfollow( &self, room_id: RoomId, diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index 178cb712ed5df6f0bcaaed45e5fd4d43996d88b1..7434e2d20d006242ef572a5605c597fa13d71ade 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -1,6 +1,7 @@ use super::*; impl Database { + /// Clears all room participants in rooms attached to a stale server. pub async fn clear_stale_room_participants( &self, room_id: RoomId, @@ -78,6 +79,7 @@ impl Database { .await } + /// Returns the incoming calls for user with the given ID. pub async fn incoming_call_for_user( &self, user_id: UserId, @@ -102,6 +104,7 @@ impl Database { .await } + /// Creates a new room. pub async fn create_room( &self, user_id: UserId, @@ -394,6 +397,7 @@ impl Database { Ok(participant_index) } + /// Returns the channel ID for the given room, if it has one. pub async fn channel_id_for_room(&self, room_id: RoomId) -> Result> { self.transaction(|tx| async move { let room: Option = room::Entity::find() @@ -944,6 +948,7 @@ impl Database { .await } + /// Updates the location of a participant in the given room. pub async fn update_room_participant_location( &self, room_id: RoomId, @@ -1004,6 +1009,7 @@ impl Database { .await } + /// Sets the role of a participant in the given room. pub async fn set_room_participant_role( &self, admin_id: UserId, diff --git a/crates/collab/src/db/queries/servers.rs b/crates/collab/src/db/queries/servers.rs index e5ceee88873e0e89ecf8a29beef587b00c9baaf9..c79b00eee88654fbbe16ae6c85db159aee4b1a0e 100644 --- a/crates/collab/src/db/queries/servers.rs +++ b/crates/collab/src/db/queries/servers.rs @@ -1,6 +1,7 @@ use super::*; impl Database { + /// Creates a new server in the given environment. pub async fn create_server(&self, environment: &str) -> Result { self.transaction(|tx| async move { let server = server::ActiveModel { @@ -14,6 +15,10 @@ impl Database { .await } + /// Returns the IDs of resources associated with stale servers. + /// + /// A server is stale if it is in the specified `environment` and does not + /// match the provided `new_server_id`. pub async fn stale_server_resource_ids( &self, environment: &str, @@ -61,6 +66,7 @@ impl Database { .await } + /// Deletes any stale servers in the environment that don't match the `new_server_id`. pub async fn delete_stale_servers( &self, environment: &str, diff --git a/crates/collab/src/db/queries/users.rs b/crates/collab/src/db/queries/users.rs index 27e64e25981ecdbd31e6aa337875e1ff81852b9c..954ec5f0d80c5b58149cd935abca648fa40a82ef 100644 --- a/crates/collab/src/db/queries/users.rs +++ b/crates/collab/src/db/queries/users.rs @@ -1,6 +1,7 @@ use super::*; impl Database { + /// Creates a new user. pub async fn create_user( &self, email_address: &str, @@ -35,11 +36,13 @@ impl Database { .await } + /// Returns a user by ID. There are no access checks here, so this should only be used internally. pub async fn get_user_by_id(&self, id: UserId) -> Result> { self.transaction(|tx| async move { Ok(user::Entity::find_by_id(id).one(&*tx).await?) }) .await } + /// Returns all users by ID. There are no access checks here, so this should only be used internally. pub async fn get_users_by_ids(&self, ids: Vec) -> Result> { self.transaction(|tx| async { let tx = tx; @@ -51,6 +54,7 @@ impl Database { .await } + /// Returns a user by GitHub login. There are no access checks here, so this should only be used internally. pub async fn get_user_by_github_login(&self, github_login: &str) -> Result> { self.transaction(|tx| async move { Ok(user::Entity::find() @@ -111,6 +115,8 @@ impl Database { .await } + /// get_all_users returns the next page of users. To get more call again with + /// the same limit and the page incremented by 1. pub async fn get_all_users(&self, page: u32, limit: u32) -> Result> { self.transaction(|tx| async move { Ok(user::Entity::find() @@ -123,6 +129,7 @@ impl Database { .await } + /// Returns the metrics id for the user. pub async fn get_user_metrics_id(&self, id: UserId) -> Result { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] enum QueryAs { @@ -142,6 +149,7 @@ impl Database { .await } + /// Set "connected_once" on the user for analytics. pub async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()> { self.transaction(|tx| async move { user::Entity::update_many() @@ -157,6 +165,7 @@ impl Database { .await } + /// hard delete the user. pub async fn destroy_user(&self, id: UserId) -> Result<()> { self.transaction(|tx| async move { access_token::Entity::delete_many() @@ -169,6 +178,7 @@ impl Database { .await } + /// Find users where github_login ILIKE name_query. pub async fn fuzzy_search_users(&self, name_query: &str, limit: u32) -> Result> { self.transaction(|tx| async { let tx = tx; @@ -193,6 +203,8 @@ impl Database { .await } + /// fuzzy_like_string creates a string for matching in-order using fuzzy_search_users. + /// e.g. "cir" would become "%c%i%r%" pub fn fuzzy_like_string(string: &str) -> String { let mut result = String::with_capacity(string.len() * 2 + 1); for c in string.chars() { @@ -205,6 +217,7 @@ impl Database { result } + /// Creates a new feature flag. pub async fn create_user_flag(&self, flag: &str) -> Result { self.transaction(|tx| async move { let flag = feature_flag::Entity::insert(feature_flag::ActiveModel { @@ -220,6 +233,7 @@ impl Database { .await } + /// Add the given user to the feature flag pub async fn add_user_flag(&self, user: UserId, flag: FlagId) -> Result<()> { self.transaction(|tx| async move { user_feature::Entity::insert(user_feature::ActiveModel { @@ -234,6 +248,7 @@ impl Database { .await } + /// Return the active flags for the user. pub async fn get_user_flags(&self, user: UserId) -> Result> { self.transaction(|tx| async move { #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] diff --git a/crates/collab/src/db/tables/user.rs b/crates/collab/src/db/tables/user.rs index 739693527f00a594f3376a6093dc8c0b1d270a8f..53866b5c54f96a2e3b42c06515acba4a341bead3 100644 --- a/crates/collab/src/db/tables/user.rs +++ b/crates/collab/src/db/tables/user.rs @@ -2,6 +2,7 @@ use crate::db::UserId; use sea_orm::entity::prelude::*; use serde::Serialize; +/// A user model. #[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel, Serialize)] #[sea_orm(table_name = "users")] pub struct Model { diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5d7f68caac9013bf491d1cd37929b1268d075aea..9406b4938a8f2795a89cd8f10238964710eb61f3 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -932,11 +932,13 @@ async fn connection_lost( Ok(()) } +/// Acknowledges a ping from a client, used to keep the connection alive. async fn ping(_: proto::Ping, response: Response, _session: Session) -> Result<()> { response.send(proto::Ack {})?; Ok(()) } +/// Create a new room for calling (outside of channels) async fn create_room( _request: proto::CreateRoom, response: Response, @@ -984,6 +986,7 @@ async fn create_room( Ok(()) } +/// Join a room from an invitation. Equivalent to joining a channel if there is one. async fn join_room( request: proto::JoinRoom, response: Response, @@ -1058,6 +1061,7 @@ async fn join_room( Ok(()) } +/// Rejoin room is used to reconnect to a room after connection errors. async fn rejoin_room( request: proto::RejoinRoom, response: Response, @@ -1249,6 +1253,7 @@ async fn rejoin_room( Ok(()) } +/// leave room disonnects from the room. async fn leave_room( _: proto::LeaveRoom, response: Response, @@ -1259,6 +1264,7 @@ async fn leave_room( Ok(()) } +/// Update the permissions of someone else in the room. async fn set_room_participant_role( request: proto::SetRoomParticipantRole, response: Response, @@ -1303,6 +1309,7 @@ async fn set_room_participant_role( Ok(()) } +/// Call someone else into the current room async fn call( request: proto::Call, response: Response, @@ -1371,6 +1378,7 @@ async fn call( Err(anyhow!("failed to ring user"))? } +/// Cancel an outgoing call. async fn cancel_call( request: proto::CancelCall, response: Response, @@ -1408,6 +1416,7 @@ async fn cancel_call( Ok(()) } +/// Decline an incoming call. async fn decline_call(message: proto::DeclineCall, session: Session) -> Result<()> { let room_id = RoomId::from_proto(message.room_id); { @@ -1439,6 +1448,7 @@ async fn decline_call(message: proto::DeclineCall, session: Session) -> Result<( Ok(()) } +/// Update other participants in the room with your current location. async fn update_participant_location( request: proto::UpdateParticipantLocation, response: Response, @@ -1459,6 +1469,7 @@ async fn update_participant_location( Ok(()) } +/// Share a project into the room. async fn share_project( request: proto::ShareProject, response: Response, @@ -1481,6 +1492,7 @@ async fn share_project( Ok(()) } +/// Unshare a project from the room. async fn unshare_project(message: proto::UnshareProject, session: Session) -> Result<()> { let project_id = ProjectId::from_proto(message.project_id); @@ -1500,6 +1512,7 @@ async fn unshare_project(message: proto::UnshareProject, session: Session) -> Re Ok(()) } +/// Join someone elses shared project. async fn join_project( request: proto::JoinProject, response: Response, @@ -1625,6 +1638,7 @@ async fn join_project( Ok(()) } +/// Leave someone elses shared project. async fn leave_project(request: proto::LeaveProject, session: Session) -> Result<()> { let sender_id = session.connection_id; let project_id = ProjectId::from_proto(request.project_id); @@ -1647,6 +1661,7 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result Ok(()) } +/// Update other participants with changes to the project async fn update_project( request: proto::UpdateProject, response: Response, @@ -1673,6 +1688,7 @@ async fn update_project( Ok(()) } +/// Update other participants with changes to the worktree async fn update_worktree( request: proto::UpdateWorktree, response: Response, @@ -1697,6 +1713,7 @@ async fn update_worktree( Ok(()) } +/// Update other participants with changes to the diagnostics async fn update_diagnostic_summary( message: proto::UpdateDiagnosticSummary, session: Session, @@ -1720,6 +1737,7 @@ async fn update_diagnostic_summary( Ok(()) } +/// Update other participants with changes to the worktree settings async fn update_worktree_settings( message: proto::UpdateWorktreeSettings, session: Session, @@ -1743,6 +1761,7 @@ async fn update_worktree_settings( Ok(()) } +/// Notify other participants that a language server has started. async fn start_language_server( request: proto::StartLanguageServer, session: Session, @@ -1765,6 +1784,7 @@ async fn start_language_server( Ok(()) } +/// Notify other participants that a language server has changed. async fn update_language_server( request: proto::UpdateLanguageServer, session: Session, @@ -1787,6 +1807,8 @@ async fn update_language_server( Ok(()) } +/// forward a project request to the host. These requests should be read only +/// as guests are allowed to send them. async fn forward_read_only_project_request( request: T, response: Response, @@ -1809,6 +1831,8 @@ where Ok(()) } +/// forward a project request to the host. These requests are disallowed +/// for guests. async fn forward_mutating_project_request( request: T, response: Response, @@ -1831,6 +1855,7 @@ where Ok(()) } +/// Notify other participants that a new buffer has been created async fn create_buffer_for_peer( request: proto::CreateBufferForPeer, session: Session, @@ -1850,6 +1875,8 @@ async fn create_buffer_for_peer( Ok(()) } +/// Notify other participants that a buffer has been updated. This is +/// allowed for guests as long as the update is limited to selections. async fn update_buffer( request: proto::UpdateBuffer, response: Response, @@ -1909,6 +1936,7 @@ async fn update_buffer( Ok(()) } +/// Notify other participants that a project has been updated. async fn broadcast_project_message_from_host>( request: T, session: Session, @@ -1932,6 +1960,7 @@ async fn broadcast_project_message_from_host, @@ -1969,6 +1998,7 @@ async fn follow( Ok(()) } +/// Stop following another user in a call. async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> { let room_id = RoomId::from_proto(request.room_id); let project_id = request.project_id.map(ProjectId::from_proto); @@ -2000,6 +2030,7 @@ async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> { Ok(()) } +/// Notify everyone following you of your current location. async fn update_followers(request: proto::UpdateFollowers, session: Session) -> Result<()> { let room_id = RoomId::from_proto(request.room_id); let database = session.db.lock().await; @@ -2036,6 +2067,7 @@ async fn update_followers(request: proto::UpdateFollowers, session: Session) -> Ok(()) } +/// Get public data about users. async fn get_users( request: proto::GetUsers, response: Response, @@ -2062,6 +2094,7 @@ async fn get_users( Ok(()) } +/// Search for users (to invite) buy Github login async fn fuzzy_search_users( request: proto::FuzzySearchUsers, response: Response, @@ -2092,6 +2125,7 @@ async fn fuzzy_search_users( Ok(()) } +/// Send a contact request to another user. async fn request_contact( request: proto::RequestContact, response: Response, @@ -2138,6 +2172,7 @@ async fn request_contact( Ok(()) } +/// Accept or decline a contact request async fn respond_to_contact_request( request: proto::RespondToContactRequest, response: Response, @@ -2195,6 +2230,7 @@ async fn respond_to_contact_request( Ok(()) } +/// Remove a contact. async fn remove_contact( request: proto::RemoveContact, response: Response, @@ -2245,6 +2281,7 @@ async fn remove_contact( Ok(()) } +/// Create a new channel. async fn create_channel( request: proto::CreateChannel, response: Response, @@ -2279,6 +2316,7 @@ async fn create_channel( Ok(()) } +/// Delete a channel async fn delete_channel( request: proto::DeleteChannel, response: Response, @@ -2308,6 +2346,7 @@ async fn delete_channel( Ok(()) } +/// Invite someone to join a channel. async fn invite_channel_member( request: proto::InviteChannelMember, response: Response, @@ -2344,6 +2383,7 @@ async fn invite_channel_member( Ok(()) } +/// remove someone from a channel async fn remove_channel_member( request: proto::RemoveChannelMember, response: Response, @@ -2385,6 +2425,7 @@ async fn remove_channel_member( Ok(()) } +/// Toggle the channel between public and private async fn set_channel_visibility( request: proto::SetChannelVisibility, response: Response, @@ -2423,6 +2464,7 @@ async fn set_channel_visibility( Ok(()) } +/// Alter the role for a user in the channel async fn set_channel_member_role( request: proto::SetChannelMemberRole, response: Response, @@ -2470,6 +2512,7 @@ async fn set_channel_member_role( Ok(()) } +/// Change the name of a channel async fn rename_channel( request: proto::RenameChannel, response: Response, @@ -2503,6 +2546,7 @@ async fn rename_channel( Ok(()) } +/// Move a channel to a new parent. async fn move_channel( request: proto::MoveChannel, response: Response, @@ -2555,6 +2599,7 @@ async fn notify_channel_moved(result: Option, session: Sessio Ok(()) } +/// Get the list of channel members async fn get_channel_members( request: proto::GetChannelMembers, response: Response, @@ -2569,6 +2614,7 @@ async fn get_channel_members( Ok(()) } +/// Accept or decline a channel invitation. async fn respond_to_channel_invite( request: proto::RespondToChannelInvite, response: Response, @@ -2609,6 +2655,7 @@ async fn respond_to_channel_invite( Ok(()) } +/// Join the channels' room async fn join_channel( request: proto::JoinChannel, response: Response, @@ -2713,6 +2760,7 @@ async fn join_channel_internal( Ok(()) } +/// Start editing the channel notes async fn join_channel_buffer( request: proto::JoinChannelBuffer, response: Response, @@ -2744,6 +2792,7 @@ async fn join_channel_buffer( Ok(()) } +/// Edit the channel notes async fn update_channel_buffer( request: proto::UpdateChannelBuffer, session: Session, @@ -2790,6 +2839,7 @@ async fn update_channel_buffer( Ok(()) } +/// Rejoin the channel notes after a connection blip async fn rejoin_channel_buffers( request: proto::RejoinChannelBuffers, response: Response, @@ -2824,6 +2874,7 @@ async fn rejoin_channel_buffers( Ok(()) } +/// Stop editing the channel notes async fn leave_channel_buffer( request: proto::LeaveChannelBuffer, response: Response, @@ -2885,6 +2936,7 @@ fn send_notifications( } } +/// Send a message to the channel async fn send_channel_message( request: proto::SendChannelMessage, response: Response, @@ -2973,6 +3025,7 @@ async fn send_channel_message( Ok(()) } +/// Delete a channel message async fn remove_channel_message( request: proto::RemoveChannelMessage, response: Response, @@ -2992,6 +3045,7 @@ async fn remove_channel_message( Ok(()) } +/// Mark a channel message as read async fn acknowledge_channel_message( request: proto::AckChannelMessage, session: Session, @@ -3011,6 +3065,7 @@ async fn acknowledge_channel_message( Ok(()) } +/// Mark a buffer version as synced async fn acknowledge_buffer_version( request: proto::AckBufferOperation, session: Session, @@ -3029,6 +3084,7 @@ async fn acknowledge_buffer_version( Ok(()) } +/// Start receiving chat updates for a channel async fn join_channel_chat( request: proto::JoinChannelChat, response: Response, @@ -3049,6 +3105,7 @@ async fn join_channel_chat( Ok(()) } +/// Stop receiving chat updates for a channel async fn leave_channel_chat(request: proto::LeaveChannelChat, session: Session) -> Result<()> { let channel_id = ChannelId::from_proto(request.channel_id); session @@ -3059,6 +3116,7 @@ async fn leave_channel_chat(request: proto::LeaveChannelChat, session: Session) Ok(()) } +/// Retrive the chat history for a channel async fn get_channel_messages( request: proto::GetChannelMessages, response: Response, @@ -3082,6 +3140,7 @@ async fn get_channel_messages( Ok(()) } +/// Retrieve specific chat messages async fn get_channel_messages_by_id( request: proto::GetChannelMessagesById, response: Response, @@ -3104,6 +3163,7 @@ async fn get_channel_messages_by_id( Ok(()) } +/// Retrieve the current users notifications async fn get_notifications( request: proto::GetNotifications, response: Response, @@ -3127,6 +3187,7 @@ async fn get_notifications( Ok(()) } +/// Mark notifications as read async fn mark_notification_as_read( request: proto::MarkNotificationRead, response: Response, @@ -3148,6 +3209,7 @@ async fn mark_notification_as_read( Ok(()) } +/// Get the current users information async fn get_private_user_info( _request: proto::GetPrivateUserInfo, response: Response, From d7503a7d47093ae973d18d0a1cd7ec85ec116e77 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 17 Jan 2024 13:39:37 -0500 Subject: [PATCH 266/334] Document LSP crate Co-Authored-By: Thorsten Ball --- crates/lsp/src/lsp.rs | 67 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 788c424373deca7c1490dd954fa005e0943d8a99..0c1574f5aaff21253c1d1eda695939c801283f2e 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -39,6 +39,7 @@ type NotificationHandler = Box, &str, AsyncAppCon type ResponseHandler = Box)>; type IoHandler = Box; +/// Kind of language server stdio given to an IO handler. #[derive(Debug, Clone, Copy)] pub enum IoKind { StdOut, @@ -46,12 +47,15 @@ pub enum IoKind { StdErr, } +/// Represents a launchable language server. This can either be a standalone binary or the path +/// to a runtime with arguments to instruct it to launch the actual language server file. #[derive(Debug, Clone, Deserialize)] pub struct LanguageServerBinary { pub path: PathBuf, pub arguments: Vec, } +/// A running language server process. pub struct LanguageServer { server_id: LanguageServerId, next_id: AtomicUsize, @@ -70,10 +74,12 @@ pub struct LanguageServer { _server: Option>, } +/// Identifies a running language server. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct LanguageServerId(pub usize); +/// Handle to a language server RPC activity subscription. pub enum Subscription { Notification { method: &'static str, @@ -85,6 +91,9 @@ pub enum Subscription { }, } +/// Language server protocol RPC request message. +/// +/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage) #[derive(Serialize, Deserialize)] pub struct Request<'a, T> { jsonrpc: &'static str, @@ -93,6 +102,7 @@ pub struct Request<'a, T> { params: T, } +/// Language server protocol RPC request response message before it is deserialized into a concrete type. #[derive(Serialize, Deserialize)] struct AnyResponse<'a> { jsonrpc: &'a str, @@ -103,6 +113,9 @@ struct AnyResponse<'a> { result: Option<&'a RawValue>, } +/// Language server protocol RPC request response message. +/// +/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#responseMessage) #[derive(Serialize)] struct Response { jsonrpc: &'static str, @@ -111,6 +124,9 @@ struct Response { error: Option, } +/// Language server protocol RPC notification message. +/// +/// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage) #[derive(Serialize, Deserialize)] struct Notification<'a, T> { jsonrpc: &'static str, @@ -119,6 +135,7 @@ struct Notification<'a, T> { params: T, } +/// Language server RPC notification message before it is deserialized into a concrete type. #[derive(Debug, Clone, Deserialize)] struct AnyNotification<'a> { #[serde(default)] @@ -135,6 +152,7 @@ struct Error { } impl LanguageServer { + /// Starts a language server process. pub fn new( stderr_capture: Arc>>, server_id: LanguageServerId, @@ -277,6 +295,7 @@ impl LanguageServer { } } + /// List of code action kinds this language server reports being able to emit. pub fn code_action_kinds(&self) -> Option> { self.code_action_kinds.clone() } @@ -427,9 +446,10 @@ impl LanguageServer { Ok(()) } - /// Initializes a language server. - /// Note that `options` is used directly to construct [`InitializeParams`], - /// which is why it is owned. + /// Initializes a language server by sending the `Initialize` request. + /// Note that `options` is used directly to construct [`InitializeParams`], which is why it is owned. + /// + /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize) pub async fn initialize(mut self, options: Option) -> Result> { let root_uri = Url::from_file_path(&self.root_path).unwrap(); #[allow(deprecated)] @@ -564,6 +584,7 @@ impl LanguageServer { Ok(Arc::new(self)) } + /// Sends a shutdown request to the language server process and prepares the `LanguageServer` to be dropped. pub fn shutdown(&self) -> Option>> { if let Some(tasks) = self.io_tasks.lock().take() { let response_handlers = self.response_handlers.clone(); @@ -598,6 +619,9 @@ impl LanguageServer { } } + /// Register a handler to handle incoming LSP notifications. + /// + /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage) #[must_use] pub fn on_notification(&self, f: F) -> Subscription where @@ -607,6 +631,9 @@ impl LanguageServer { self.on_custom_notification(T::METHOD, f) } + /// Register a handler to handle incoming LSP requests. + /// + /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage) #[must_use] pub fn on_request(&self, f: F) -> Subscription where @@ -618,6 +645,7 @@ impl LanguageServer { self.on_custom_request(T::METHOD, f) } + /// Register a handler to inspect all language server process stdio. #[must_use] pub fn on_io(&self, f: F) -> Subscription where @@ -631,20 +659,23 @@ impl LanguageServer { } } + /// Removes a request handler registers via [Self::on_request]. pub fn remove_request_handler(&self) { self.notification_handlers.lock().remove(T::METHOD); } + /// Removes a notification handler registers via [Self::on_notification]. pub fn remove_notification_handler(&self) { self.notification_handlers.lock().remove(T::METHOD); } + /// Checks if a notification handler has been registered via [Self::on_notification]. pub fn has_notification_handler(&self) -> bool { self.notification_handlers.lock().contains_key(T::METHOD) } #[must_use] - pub fn on_custom_notification(&self, method: &'static str, mut f: F) -> Subscription + fn on_custom_notification(&self, method: &'static str, mut f: F) -> Subscription where F: 'static + FnMut(Params, AsyncAppContext) + Send, Params: DeserializeOwned, @@ -668,11 +699,7 @@ impl LanguageServer { } #[must_use] - pub fn on_custom_request( - &self, - method: &'static str, - mut f: F, - ) -> Subscription + fn on_custom_request(&self, method: &'static str, mut f: F) -> Subscription where F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send, Fut: 'static + Future>, @@ -750,22 +777,29 @@ impl LanguageServer { } } + /// Get the name of the running language server. pub fn name(&self) -> &str { &self.name } + /// Get the reported capabilities of the running language server. pub fn capabilities(&self) -> &ServerCapabilities { &self.capabilities } + /// Get the id of the running language server. pub fn server_id(&self) -> LanguageServerId { self.server_id } + /// Get the root path of the project the language server is running against. pub fn root_path(&self) -> &PathBuf { &self.root_path } + /// Sends a RPC request to the language server. + /// + /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#requestMessage) pub fn request( &self, params: T::Params, @@ -851,6 +885,9 @@ impl LanguageServer { } } + /// Sends a RPC notification to the language server. + /// + /// [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notificationMessage) pub fn notify(&self, params: T::Params) -> Result<()> { Self::notify_internal::(&self.outbound_tx, params) } @@ -879,6 +916,7 @@ impl Drop for LanguageServer { } impl Subscription { + /// Detaching a subscription handle prevents it from unsubscribing on drop. pub fn detach(&mut self) { match self { Subscription::Notification { @@ -925,6 +963,7 @@ impl Drop for Subscription { } } +/// Mock language server for use in tests. #[cfg(any(test, feature = "test-support"))] #[derive(Clone)] pub struct FakeLanguageServer { @@ -946,6 +985,7 @@ impl LanguageServer { } } + /// Construct a fake language server. pub fn fake( name: String, capabilities: ServerCapabilities, @@ -1015,10 +1055,12 @@ impl LanguageServer { #[cfg(any(test, feature = "test-support"))] impl FakeLanguageServer { + /// See [LanguageServer::notify] pub fn notify(&self, params: T::Params) { self.server.notify::(params).ok(); } + /// See [LanguageServer::request] pub async fn request(&self, params: T::Params) -> Result where T: request::Request, @@ -1028,11 +1070,13 @@ impl FakeLanguageServer { self.server.request::(params).await } + /// Attempts [try_receive_notification], unwrapping if it has not received the specified type yet. pub async fn receive_notification(&mut self) -> T::Params { self.server.executor.start_waiting(); self.try_receive_notification::().await.unwrap() } + /// Consumes the notification channel until it finds a notification for the specified type. pub async fn try_receive_notification( &mut self, ) -> Option { @@ -1048,6 +1092,7 @@ impl FakeLanguageServer { } } + /// Registers a handler for a specific kind of request. Removes any existing handler for specified request type. pub fn handle_request( &self, mut handler: F, @@ -1076,6 +1121,7 @@ impl FakeLanguageServer { responded_rx } + /// Registers a handler for a specific kind of notification. Removes any existing handler for specified notification type. pub fn handle_notification( &self, mut handler: F, @@ -1096,6 +1142,7 @@ impl FakeLanguageServer { handled_rx } + /// Removes any existing handler for specified notification type. pub fn remove_request_handler(&mut self) where T: 'static + request::Request, @@ -1103,6 +1150,7 @@ impl FakeLanguageServer { self.server.remove_request_handler::(); } + /// Simulate that the server has started work and notifies about its progress with the specified token. pub async fn start_progress(&self, token: impl Into) { let token = token.into(); self.request::(WorkDoneProgressCreateParams { @@ -1116,6 +1164,7 @@ impl FakeLanguageServer { }); } + /// Simulate that the server has completed work and notifies about that with the specified token. pub fn end_progress(&self, token: impl Into) { self.notify::(ProgressParams { token: NumberOrString::String(token.into()), From 552d2c26f5096e89632e4b6b1b51c23258f49b2d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 17 Jan 2024 11:41:42 -0700 Subject: [PATCH 267/334] Also update chat location when opening a new workspace This happens a lot in guest workflows where they open the call with a link and are jumped straight to a shared workspace. --- crates/collab_ui/src/chat_panel.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 140d5be4c6548664d1518ff3b6eff5583d3e22ab..44b0669c307bcb491e53e0f07ab48ae461d7f6b3 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -125,6 +125,23 @@ impl ChatPanel { open_context_menu: None, }; + if let Some(channel_id) = ActiveCall::global(cx) + .read(cx) + .room() + .and_then(|room| room.read(cx).channel_id()) + { + this.select_channel(channel_id, None, cx) + .detach_and_log_err(cx); + + if ActiveCall::global(cx) + .read(cx) + .room() + .is_some_and(|room| room.read(cx).contains_guests()) + { + cx.emit(PanelEvent::Activate) + } + } + this.subscriptions.push(cx.subscribe( &ActiveCall::global(cx), move |this: &mut Self, call, event: &room::Event, cx| match event { From 306e4693fad8355c2e5d83f516de70e86449aded Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 17 Jan 2024 15:11:33 +0200 Subject: [PATCH 268/334] Start adding project search listeners to workspace co-authored-by: Piotr To be able to trigger them from search multibuffer excerpts. --- crates/assistant/src/assistant_panel.rs | 2 +- crates/search/src/buffer_search.rs | 72 +++++++++++++++++----- crates/search/src/project_search.rs | 70 ++++++++++++++++++++- crates/terminal_view/src/terminal_panel.rs | 2 +- 4 files changed, 129 insertions(+), 17 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index df3dc3754f66aff8d83a6fcd3b92edd38c7c4e45..5a8376554e9c66db347dc7012e45208a6ab5f157 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1148,7 +1148,7 @@ impl Render for AssistantPanel { |panel, cx| panel.toolbar.read(cx).item_of_type::(), cx, ); - BufferSearchBar::register_inner(&mut registrar); + BufferSearchBar::register(&mut registrar); registrar.into_div() } else { div() diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index e217a7ab73cd2fd1aaa540f1b56cf13b7ec1c84b..4c6d9a5708e3f2965a223a26a979258822ab446e 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -496,67 +496,111 @@ impl SearchActionsRegistrar for Workspace { }); } } + impl BufferSearchBar { - pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) { + pub fn register(registrar: &mut impl SearchActionsRegistrar) { registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + if this.supported_options().case { this.toggle_case_sensitive(action, cx); } }); - registrar.register_handler(|this, action: &ToggleWholeWord, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + if this.supported_options().word { this.toggle_whole_word(action, cx); } }); - registrar.register_handler(|this, action: &ToggleReplace, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + if this.supported_options().replacement { this.toggle_replace(action, cx); } }); - registrar.register_handler(|this, _: &ActivateRegexMode, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + if this.supported_options().regex { this.activate_search_mode(SearchMode::Regex, cx); } }); - registrar.register_handler(|this, _: &ActivateTextMode, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + this.activate_search_mode(SearchMode::Text, cx); }); - registrar.register_handler(|this, action: &CycleMode, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + if this.supported_options().regex { // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting // cycling. this.cycle_mode(action, cx) } }); - registrar.register_handler(|this, action: &SelectNextMatch, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + this.select_next_match(action, cx); }); registrar.register_handler(|this, action: &SelectPrevMatch, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + this.select_prev_match(action, cx); }); registrar.register_handler(|this, action: &SelectAllMatches, cx| { + if this.is_dismissed() { + cx.propagate(); + return; + } + this.select_all_matches(action, cx); }); registrar.register_handler(|this, _: &editor::Cancel, cx| { - if this.dismissed { + if this.is_dismissed() { cx.propagate(); - } else { - this.dismiss(&Dismiss, cx); + return; } + + this.dismiss(&Dismiss, cx); }); registrar.register_handler(|this, deploy, cx| { - this.deploy(deploy, cx); + if this.is_dismissed() { + this.deploy(deploy, cx); + return; + } + + cx.propagate(); }) } - fn register(workspace: &mut Workspace) { - Self::register_inner(workspace); - } + pub fn new(cx: &mut ViewContext) -> Self { let query_editor = cx.new_view(|cx| Editor::single_line(cx)); cx.subscribe(&query_editor, Self::on_query_editor_event) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 49eb24ce9ee9e267dc921b6ddb8eb10e92c83c97..30c29a0c089c067d1482074a7940892b3497929d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -63,7 +63,57 @@ pub fn init(cx: &mut AppContext) { workspace .register_action(ProjectSearchView::new_search) .register_action(ProjectSearchView::deploy_search) - .register_action(ProjectSearchBar::search_in_new); + .register_action(ProjectSearchBar::search_in_new) + // TODO kb register these too, consider having the methods for &Workspace for that, as above + // ToggleCaseSensitive + // ToggleWholeWord + // ToggleReplace + // ActivateRegexMode + // SelectPrevMatch + // ActivateTextMode + // ActivateSemanticMode + // CycleMode + // SelectNextMatch (see a proto below) + /* + // Have a generic method similar to the registrar has: + fn register_workspace_action( + &mut workspace, + callback: fn(&mut ProjectSearchBar, &A, &mut ViewContext), + ) { + workspace.register_action(move |workspace, action: &A, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + if let Some(search_bar) = workspace.active_item(cx).and_then(|item| item.downcast::()) { + search_bar.update(cx, move |this, cx| callback(this, action, cx)); + cx.notify(); + } + }); + } + */ + .register_action(move |workspace, action: &SelectNextMatch, cx| { + dbg!("@@@@@@@@@1"); + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + + dbg!("????? 2"); + let pane = workspace.active_pane(); + pane.update(cx, move |this, cx| { + this.toolbar().update(cx, move |this, cx| { + dbg!("@@@@@@@@@ 3"); + if let Some(search_bar) = this.item_of_type::() { + dbg!("$$$$$$$$$ 4"); + search_bar.update(cx, move |search_bar, cx| { + search_bar.select_next_match(action, cx) + }); + cx.notify(); + } + }) + }); + }); }) .detach(); } @@ -1502,6 +1552,22 @@ impl ProjectSearchBar { } } + pub fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext) { + if let Some(search) = self.active_project_search.as_ref() { + search.update(cx, |this, cx| { + this.select_match(Direction::Next, cx); + }) + } + } + + fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext) { + if let Some(search) = self.active_project_search.as_ref() { + search.update(cx, |this, cx| { + this.select_match(Direction::Prev, cx); + }) + } + } + fn new_placeholder_text(&self, cx: &mut ViewContext) -> Option { let previous_query_keystrokes = cx .bindings_for_action(&PreviousHistoryQuery {}) @@ -1870,6 +1936,8 @@ impl Render for ProjectSearchBar { })) }) }) + .on_action(cx.listener(Self::select_next_match)) + .on_action(cx.listener(Self::select_prev_match)) .child( h_flex() .justify_between() diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 8954e70e8fc18d3d78775ba4eb56b11ec251c0de..7a988851d8a34b8533631e1f74dfcb5d73c45ed1 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -387,7 +387,7 @@ impl Render for TerminalPanel { }, cx, ); - BufferSearchBar::register_inner(&mut registrar); + BufferSearchBar::register(&mut registrar); registrar.into_div().size_full().child(self.pane.clone()) } } From 0be2f7f32891c628fbb02771cfcf7f1453804960 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 17 Jan 2024 21:09:28 +0200 Subject: [PATCH 269/334] Properly register buffer_search'es actions handlers Now those handlers do not intercept events/actions when the buffer search bar is dismissed. co-authored-by: Piotr --- crates/search/src/buffer_search.rs | 162 +++++++++++++++++----------- crates/search/src/project_search.rs | 4 - 2 files changed, 100 insertions(+), 66 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 4c6d9a5708e3f2965a223a26a979258822ab446e..ee395328c38b459ecff5e19e7d47d97ca719f692 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -429,6 +429,11 @@ pub trait SearchActionsRegistrar { &mut self, callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ); + + fn register_handler_for_dismissed_bar( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ); } type GetSearchBar = @@ -457,16 +462,60 @@ impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> { } impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { - fn register_handler( + fn register_handler( &mut self, callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ) { let getter = self.search_getter; self.div = self.div.take().map(|div| { div.on_action(self.cx.listener(move |this, action, cx| { - (getter)(this, cx) + let should_notify = (getter)(this, cx) .clone() - .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); + .map(|search_bar| { + search_bar.update(cx, |search_bar, cx| { + if search_bar.is_dismissed() { + false + } else { + callback(search_bar, action, cx); + true + } + }) + }) + .unwrap_or(false); + if should_notify { + cx.notify(); + } else { + cx.propagate(); + } + })) + }); + } + + fn register_handler_for_dismissed_bar( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + let getter = self.search_getter; + self.div = self.div.take().map(|div| { + div.on_action(self.cx.listener(move |this, action, cx| { + let should_notify = (getter)(this, cx) + .clone() + .map(|search_bar| { + search_bar.update(cx, |search_bar, cx| { + if search_bar.is_dismissed() { + callback(search_bar, action, cx); + true + } else { + false + } + }) + }) + .unwrap_or(false); + if should_notify { + cx.notify(); + } else { + cx.propagate(); + } })) }); } @@ -488,71 +537,85 @@ impl SearchActionsRegistrar for Workspace { pane.update(cx, move |this, cx| { this.toolbar().update(cx, move |this, cx| { if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, move |this, cx| callback(this, action, cx)); - cx.notify(); + let should_notify = search_bar.update(cx, move |search_bar, cx| { + if search_bar.is_dismissed() { + false + } else { + callback(search_bar, action, cx); + true + } + }); + if should_notify { + cx.notify(); + } else { + cx.propagate(); + } } }) }); }); } -} -impl BufferSearchBar { - pub fn register(registrar: &mut impl SearchActionsRegistrar) { - registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { - if this.is_dismissed() { + fn register_handler_for_dismissed_bar( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.register_action(move |workspace, action: &A, cx| { + if workspace.has_active_modal(cx) { cx.propagate(); return; } + let pane = workspace.active_pane(); + pane.update(cx, move |this, cx| { + this.toolbar().update(cx, move |this, cx| { + if let Some(search_bar) = this.item_of_type::() { + let should_notify = search_bar.update(cx, move |search_bar, cx| { + if search_bar.is_dismissed() { + callback(search_bar, action, cx); + true + } else { + false + } + }); + if should_notify { + cx.notify(); + } else { + cx.propagate(); + } + } + }) + }); + }); + } +} + +impl BufferSearchBar { + pub fn register(registrar: &mut impl SearchActionsRegistrar) { + registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { if this.supported_options().case { this.toggle_case_sensitive(action, cx); } }); registrar.register_handler(|this, action: &ToggleWholeWord, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - if this.supported_options().word { this.toggle_whole_word(action, cx); } }); registrar.register_handler(|this, action: &ToggleReplace, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - if this.supported_options().replacement { this.toggle_replace(action, cx); } }); registrar.register_handler(|this, _: &ActivateRegexMode, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - if this.supported_options().regex { this.activate_search_mode(SearchMode::Regex, cx); } }); registrar.register_handler(|this, _: &ActivateTextMode, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - this.activate_search_mode(SearchMode::Text, cx); }); registrar.register_handler(|this, action: &CycleMode, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - if this.supported_options().regex { // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting // cycling. @@ -560,44 +623,19 @@ impl BufferSearchBar { } }); registrar.register_handler(|this, action: &SelectNextMatch, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - this.select_next_match(action, cx); }); registrar.register_handler(|this, action: &SelectPrevMatch, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - this.select_prev_match(action, cx); }); registrar.register_handler(|this, action: &SelectAllMatches, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - this.select_all_matches(action, cx); }); registrar.register_handler(|this, _: &editor::Cancel, cx| { - if this.is_dismissed() { - cx.propagate(); - return; - } - this.dismiss(&Dismiss, cx); }); - registrar.register_handler(|this, deploy, cx| { - if this.is_dismissed() { - this.deploy(deploy, cx); - return; - } - - cx.propagate(); + registrar.register_handler_for_dismissed_bar(|this, deploy, cx| { + this.deploy(deploy, cx); }) } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 30c29a0c089c067d1482074a7940892b3497929d..a13f70fbe8a394c7b6a092fe4ce4ddf155128312 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -93,19 +93,15 @@ pub fn init(cx: &mut AppContext) { } */ .register_action(move |workspace, action: &SelectNextMatch, cx| { - dbg!("@@@@@@@@@1"); if workspace.has_active_modal(cx) { cx.propagate(); return; } - dbg!("????? 2"); let pane = workspace.active_pane(); pane.update(cx, move |this, cx| { this.toolbar().update(cx, move |this, cx| { - dbg!("@@@@@@@@@ 3"); if let Some(search_bar) = this.item_of_type::() { - dbg!("$$$$$$$$$ 4"); search_bar.update(cx, move |search_bar, cx| { search_bar.select_next_match(action, cx) }); From 65be90937887fcbfb114e7e7a0154579ff371eff Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 17 Jan 2024 22:07:00 +0200 Subject: [PATCH 270/334] Implement similar workspace registration flow for project search actions --- crates/search/src/buffer_search.rs | 8 +- crates/search/src/project_search.rs | 241 +++++++++++++++++----------- 2 files changed, 153 insertions(+), 96 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index ee395328c38b459ecff5e19e7d47d97ca719f692..e4a68b6105eb0ca6a55e89136822041524391820 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -430,7 +430,7 @@ pub trait SearchActionsRegistrar { callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ); - fn register_handler_for_dismissed_bar( + fn register_handler_for_dismissed_search( &mut self, callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ); @@ -491,7 +491,7 @@ impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { }); } - fn register_handler_for_dismissed_bar( + fn register_handler_for_dismissed_search( &mut self, callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ) { @@ -556,7 +556,7 @@ impl SearchActionsRegistrar for Workspace { }); } - fn register_handler_for_dismissed_bar( + fn register_handler_for_dismissed_search( &mut self, callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), ) { @@ -634,7 +634,7 @@ impl BufferSearchBar { registrar.register_handler(|this, _: &editor::Cancel, cx| { this.dismiss(&Dismiss, cx); }); - registrar.register_handler_for_dismissed_bar(|this, deploy, cx| { + registrar.register_handler_for_dismissed_search(|this, deploy, cx| { this.deploy(deploy, cx); }) } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a13f70fbe8a394c7b6a092fe4ce4ddf155128312..098fa184e353ab58b05a6e5709b0eb62f1a5a16f 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -12,10 +12,10 @@ use editor::{ }; use editor::{EditorElement, EditorStyle}; use gpui::{ - actions, div, AnyElement, AnyView, AppContext, Context as _, Element, EntityId, EventEmitter, - FocusHandle, FocusableView, FontStyle, FontWeight, Hsla, InteractiveElement, IntoElement, - KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, Styled, - Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView, + actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId, + EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Hsla, InteractiveElement, + IntoElement, KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, + Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext, }; use menu::Confirm; @@ -36,6 +36,7 @@ use std::{ time::{Duration, Instant}, }; use theme::ThemeSettings; +use workspace::{DeploySearch, NewSearch}; use ui::{ h_flex, prelude::*, v_flex, Icon, IconButton, IconName, Label, LabelCommon, LabelSize, @@ -60,56 +61,64 @@ struct ActiveSettings(HashMap, ProjectSearchSettings>); pub fn init(cx: &mut AppContext) { cx.set_global(ActiveSettings::default()); cx.observe_new_views(|workspace: &mut Workspace, _cx| { - workspace - .register_action(ProjectSearchView::new_search) - .register_action(ProjectSearchView::deploy_search) - .register_action(ProjectSearchBar::search_in_new) - // TODO kb register these too, consider having the methods for &Workspace for that, as above - // ToggleCaseSensitive - // ToggleWholeWord - // ToggleReplace - // ActivateRegexMode - // SelectPrevMatch - // ActivateTextMode - // ActivateSemanticMode - // CycleMode - // SelectNextMatch (see a proto below) - /* - // Have a generic method similar to the registrar has: - fn register_workspace_action( - &mut workspace, - callback: fn(&mut ProjectSearchBar, &A, &mut ViewContext), - ) { - workspace.register_action(move |workspace, action: &A, cx| { - if workspace.has_active_modal(cx) { - cx.propagate(); - return; - } - if let Some(search_bar) = workspace.active_item(cx).and_then(|item| item.downcast::()) { - search_bar.update(cx, move |this, cx| callback(this, action, cx)); - cx.notify(); - } - }); - } - */ - .register_action(move |workspace, action: &SelectNextMatch, cx| { - if workspace.has_active_modal(cx) { - cx.propagate(); - return; - } + register_workspace_action(workspace, move |search_bar, _: &ToggleFilters, cx| { + search_bar.toggle_filters(cx); + }); + register_workspace_action(workspace, move |search_bar, _: &ToggleCaseSensitive, cx| { + search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx); + }); + register_workspace_action(workspace, move |search_bar, _: &ToggleWholeWord, cx| { + search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx); + }); + register_workspace_action(workspace, move |search_bar, action: &ToggleReplace, cx| { + search_bar.toggle_replace(action, cx) + }); + register_workspace_action(workspace, move |search_bar, _: &ActivateRegexMode, cx| { + search_bar.activate_search_mode(SearchMode::Regex, cx) + }); + register_workspace_action(workspace, move |search_bar, _: &ActivateTextMode, cx| { + search_bar.activate_search_mode(SearchMode::Text, cx) + }); + register_workspace_action( + workspace, + move |search_bar, _: &ActivateSemanticMode, cx| { + search_bar.activate_search_mode(SearchMode::Semantic, cx) + }, + ); + register_workspace_action(workspace, move |search_bar, action: &CycleMode, cx| { + search_bar.cycle_mode(action, cx) + }); + register_workspace_action( + workspace, + move |search_bar, action: &SelectNextMatch, cx| { + search_bar.select_next_match(action, cx) + }, + ); + register_workspace_action( + workspace, + move |search_bar, action: &SelectPrevMatch, cx| { + search_bar.select_prev_match(action, cx) + }, + ); - let pane = workspace.active_pane(); - pane.update(cx, move |this, cx| { - this.toolbar().update(cx, move |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, move |search_bar, cx| { - search_bar.select_next_match(action, cx) - }); - cx.notify(); - } - }) - }); - }); + register_workspace_action_for_dismissed_search( + workspace, + move |workspace, action: &NewSearch, cx| { + ProjectSearchView::new_search(workspace, action, cx) + }, + ); + register_workspace_action_for_dismissed_search( + workspace, + move |workspace, action: &DeploySearch, cx| { + ProjectSearchView::deploy_search(workspace, action, cx) + }, + ); + register_workspace_action_for_dismissed_search( + workspace, + move |workspace, action: &SearchInNew, cx| { + ProjectSearchView::search_in_new(workspace, action, cx) + }, + ); }) .detach(); } @@ -1006,6 +1015,37 @@ impl ProjectSearchView { Self::existing_or_new_search(workspace, existing, cx) } + fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext) { + if let Some(search_view) = workspace + .active_item(cx) + .and_then(|item| item.downcast::()) + { + let new_query = search_view.update(cx, |search_view, cx| { + let new_query = search_view.build_search_query(cx); + if new_query.is_some() { + if let Some(old_query) = search_view.model.read(cx).active_query.clone() { + search_view.query_editor.update(cx, |editor, cx| { + editor.set_text(old_query.as_str(), cx); + }); + search_view.search_options = SearchOptions::from_query(&old_query); + } + } + new_query + }); + if let Some(new_query) = new_query { + let model = cx.new_model(|cx| { + let mut model = ProjectSearch::new(workspace.project().clone(), cx); + model.search(new_query, cx); + model + }); + workspace.add_item( + Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))), + cx, + ); + } + } + } + // Add another search tab to the workspace. fn new_search( workspace: &mut Workspace, @@ -1308,17 +1348,11 @@ impl ProjectSearchView { } } -impl Default for ProjectSearchBar { - fn default() -> Self { - Self::new() - } -} - impl ProjectSearchBar { pub fn new() -> Self { Self { - active_project_search: Default::default(), - subscription: Default::default(), + active_project_search: None, + subscription: None, } } @@ -1349,37 +1383,6 @@ impl ProjectSearchBar { } } - fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext) { - if let Some(search_view) = workspace - .active_item(cx) - .and_then(|item| item.downcast::()) - { - let new_query = search_view.update(cx, |search_view, cx| { - let new_query = search_view.build_search_query(cx); - if new_query.is_some() { - if let Some(old_query) = search_view.model.read(cx).active_query.clone() { - search_view.query_editor.update(cx, |editor, cx| { - editor.set_text(old_query.as_str(), cx); - }); - search_view.search_options = SearchOptions::from_query(&old_query); - } - } - new_query - }); - if let Some(new_query) = new_query { - let model = cx.new_model(|cx| { - let mut model = ProjectSearch::new(workspace.project().clone(), cx); - model.search(new_query, cx); - model - }); - workspace.add_item( - Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))), - cx, - ); - } - } - } - fn tab(&mut self, _: &editor::Tab, cx: &mut ViewContext) { self.cycle_field(Direction::Next, cx); } @@ -2027,6 +2030,60 @@ impl ToolbarItemView for ProjectSearchBar { } } +fn register_workspace_action( + workspace: &mut Workspace, + callback: fn(&mut ProjectSearchBar, &A, &mut ViewContext), +) { + workspace.register_action(move |workspace, action: &A, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + + workspace.active_pane().update(cx, |pane, cx| { + pane.toolbar().update(cx, move |workspace, cx| { + if let Some(search_bar) = workspace.item_of_type::() { + search_bar.update(cx, move |search_bar, cx| { + if search_bar.active_project_search.is_some() { + callback(search_bar, action, cx); + cx.notify(); + } else { + cx.propagate(); + } + }); + } + }); + }) + }); +} + +fn register_workspace_action_for_dismissed_search( + workspace: &mut Workspace, + callback: fn(&mut Workspace, &A, &mut ViewContext), +) { + workspace.register_action(move |workspace, action: &A, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + + let should_notify = workspace + .active_pane() + .read(cx) + .toolbar() + .read(cx) + .item_of_type::() + .map(|search_bar| search_bar.read(cx).active_project_search.is_none()) + .unwrap_or(false); + if should_notify { + callback(workspace, action, cx); + cx.notify(); + } else { + cx.propagate(); + } + }); +} + #[cfg(test)] pub mod tests { use super::*; From 1e6757755ef5b34089d90aa8733a248fa8afe74f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 17 Jan 2024 22:18:54 +0200 Subject: [PATCH 271/334] Ignore buffer search events if it's not for the current buffer --- crates/search/src/buffer_search.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index e4a68b6105eb0ca6a55e89136822041524391820..fbc7101355e08a0701f71559e98b4711a076d38d 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -473,7 +473,9 @@ impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { .clone() .map(|search_bar| { search_bar.update(cx, |search_bar, cx| { - if search_bar.is_dismissed() { + if search_bar.is_dismissed() + || search_bar.active_searchable_item.is_none() + { false } else { callback(search_bar, action, cx); @@ -538,7 +540,9 @@ impl SearchActionsRegistrar for Workspace { this.toolbar().update(cx, move |this, cx| { if let Some(search_bar) = this.item_of_type::() { let should_notify = search_bar.update(cx, move |search_bar, cx| { - if search_bar.is_dismissed() { + if search_bar.is_dismissed() + || search_bar.active_searchable_item.is_none() + { false } else { callback(search_bar, action, cx); From b2afa7332149a5c8d8999e75df2988b26c7b0b9b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Jan 2024 15:45:17 -0500 Subject: [PATCH 272/334] Decrease the size of timestamps in the assistant conversation editor (#4101) This PR decreases the size of the timestamps in the assistant's conversation editor. Ideally we'd want to align the baseline of the timestamp text with the text in the sender button. I spent a while trying to do this, but it seems like it may be pretty tricky. Release Notes: - Decreased the size of timestamps in the assistant panel conversation editor. --- crates/assistant/src/assistant_panel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 5a8376554e9c66db347dc7012e45208a6ab5f157..241b9af923e44da0139bf07d3a57631c6c7e353b 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2311,8 +2311,7 @@ impl ConversationEditor { } }); - div() - .h_flex() + h_flex() .id(("message_header", message_id.0)) .h_11() .relative() @@ -2328,6 +2327,7 @@ impl ConversationEditor { .add_suffix(true) .to_string(), ) + .size(LabelSize::XSmall) .color(Color::Muted), ) .children( From 9367f719f267f7981684cdbdb32b114ca51d9e4d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 12:45:18 -0800 Subject: [PATCH 273/334] Rework db-seeding, so that it doesn't depend on a github auth token Instead, admins are specified using a JSON file, 'admins.json'. This file is gitignored. If it is not present, there is a default list of admins in 'admins.default.json'. --- .gitignore | 1 + crates/collab/.admins.default.json | 1 + crates/collab/src/bin/seed.rs | 121 ++++++++++++-------------- crates/collab/src/db/queries/users.rs | 6 +- script/seed-db | 4 - script/zed-local | 39 +++++---- 6 files changed, 84 insertions(+), 88 deletions(-) create mode 100644 crates/collab/.admins.default.json diff --git a/.gitignore b/.gitignore index 2d8807a4b0559751ff341eacf7dfaf51c84c405c..6923b060f6ac1f1fc50b423100edbfd5006d5909 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /styles/src/types/zed.ts /crates/theme/schemas/theme.json /crates/collab/static/styles.css +/crates/collab/.admins.json /vendor/bin /assets/themes/*.json /assets/*licenses.md diff --git a/crates/collab/.admins.default.json b/crates/collab/.admins.default.json new file mode 100644 index 0000000000000000000000000000000000000000..6ee4d8726a303be4457078be9353402cbd712f20 --- /dev/null +++ b/crates/collab/.admins.default.json @@ -0,0 +1 @@ +["nathansobo", "as-cii", "maxbrunsfeld", "iamnbutler"] diff --git a/crates/collab/src/bin/seed.rs b/crates/collab/src/bin/seed.rs index 88fe0a647b8924b2df1312aa8a9a3bd68b5d99f1..ed24ccef75dce446eb54a431d1371139d67b7140 100644 --- a/crates/collab/src/bin/seed.rs +++ b/crates/collab/src/bin/seed.rs @@ -1,7 +1,11 @@ -use collab::{db, executor::Executor}; +use collab::{ + db::{self, NewUserParams}, + env::load_dotenv, + executor::Executor, +}; use db::{ConnectOptions, Database}; use serde::{de::DeserializeOwned, Deserialize}; -use std::fmt::Write; +use std::{fmt::Write, fs}; #[derive(Debug, Deserialize)] struct GitHubUser { @@ -12,90 +16,75 @@ struct GitHubUser { #[tokio::main] async fn main() { + load_dotenv().expect("failed to load .env.toml file"); + + let mut admin_logins = + load_admins("./.admins.default.json").expect("failed to load default admins file"); + if let Ok(other_admins) = load_admins("./.admins.json") { + admin_logins.extend(other_admins); + } + let database_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL env var"); let db = Database::new(ConnectOptions::new(database_url), Executor::Production) .await .expect("failed to connect to postgres database"); - let github_token = std::env::var("GITHUB_TOKEN").expect("missing GITHUB_TOKEN env var"); let client = reqwest::Client::new(); - let mut current_user = - fetch_github::(&client, &github_token, "https://api.github.com/user").await; - current_user - .email - .get_or_insert_with(|| "placeholder@example.com".to_string()); - let staff_users = fetch_github::>( - &client, - &github_token, - "https://api.github.com/orgs/zed-industries/teams/staff/members", - ) - .await; - - let mut zed_users = Vec::new(); - zed_users.push((current_user, true)); - zed_users.extend(staff_users.into_iter().map(|user| (user, true))); + // Create admin users for all of the users in `.admins.toml` or `.admins.default.toml`. + for admin_login in admin_logins { + let user = fetch_github::( + &client, + &format!("https://api.github.com/users/{admin_login}"), + ) + .await; + db.create_user( + &user.email.unwrap_or(format!("{admin_login}@example.com")), + true, + NewUserParams { + github_login: user.login, + github_user_id: user.id, + }, + ) + .await + .expect("failed to create admin user"); + } - let user_count = db + // Fetch 100 other random users from GitHub and insert them into the database. + let mut user_count = db .get_all_users(0, 200) .await .expect("failed to load users from db") .len(); - if user_count < 100 { - let mut last_user_id = None; - for _ in 0..10 { - let mut uri = "https://api.github.com/users?per_page=100".to_string(); - if let Some(last_user_id) = last_user_id { - write!(&mut uri, "&since={}", last_user_id).unwrap(); - } - let users = fetch_github::>(&client, &github_token, &uri).await; - if let Some(last_user) = users.last() { - last_user_id = Some(last_user.id); - zed_users.extend(users.into_iter().map(|user| (user, false))); - } else { - break; - } + let mut last_user_id = None; + while user_count < 100 { + let mut uri = "https://api.github.com/users?per_page=100".to_string(); + if let Some(last_user_id) = last_user_id { + write!(&mut uri, "&since={}", last_user_id).unwrap(); } - } + let users = fetch_github::>(&client, &uri).await; - for (github_user, admin) in zed_users { - if db - .get_user_by_github_login(&github_user.login) + for github_user in users { + last_user_id = Some(github_user.id); + user_count += 1; + db.get_or_create_user_by_github_account( + &github_user.login, + Some(github_user.id), + github_user.email.as_deref(), + ) .await - .expect("failed to fetch user") - .is_none() - { - if admin { - db.create_user( - &format!("{}@zed.dev", github_user.login), - admin, - db::NewUserParams { - github_login: github_user.login, - github_user_id: github_user.id, - }, - ) - .await - .expect("failed to insert user"); - } else { - db.get_or_create_user_by_github_account( - &github_user.login, - Some(github_user.id), - github_user.email.as_deref(), - ) - .await - .expect("failed to insert user"); - } + .expect("failed to insert user"); } } } -async fn fetch_github( - client: &reqwest::Client, - access_token: &str, - url: &str, -) -> T { +fn load_admins(path: &str) -> anyhow::Result> { + let file_content = fs::read_to_string(path)?; + Ok(serde_json::from_str(&file_content)?) +} + +async fn fetch_github(client: &reqwest::Client, url: &str) -> T { let response = client .get(url) - .bearer_auth(&access_token) .header("user-agent", "zed") .send() .await diff --git a/crates/collab/src/db/queries/users.rs b/crates/collab/src/db/queries/users.rs index 954ec5f0d80c5b58149cd935abca648fa40a82ef..8f975b5cbe5d8b4239e34e8770b57b979d6ac378 100644 --- a/crates/collab/src/db/queries/users.rs +++ b/crates/collab/src/db/queries/users.rs @@ -20,7 +20,11 @@ impl Database { }) .on_conflict( OnConflict::column(user::Column::GithubLogin) - .update_column(user::Column::GithubLogin) + .update_columns([ + user::Column::Admin, + user::Column::EmailAddress, + user::Column::GithubUserId, + ]) .to_owned(), ) .exec_with_returning(&*tx) diff --git a/script/seed-db b/script/seed-db index 6bb0f969330fec3936d2008c6013e9b37b8437cd..277ea89ba3b8e8dc056f6fab052531cec86cf102 100755 --- a/script/seed-db +++ b/script/seed-db @@ -2,8 +2,4 @@ set -e cd crates/collab - -# Export contents of .env.toml -eval "$(cargo run --quiet --bin dotenv)" - cargo run --quiet --package=collab --features seed-support --bin seed -- $@ diff --git a/script/zed-local b/script/zed-local index 090fbd58760cd04779340be19d06a199d2a0a01d..4ae4013a4c3fe7b4eaee1b24b7ea8c5bc70a2259 100755 --- a/script/zed-local +++ b/script/zed-local @@ -4,6 +4,11 @@ const HELP = ` USAGE zed-local [options] [zed args] +SUMMARY + Runs 1-4 instances of Zed using a locally-running collaboration server. + Each instance of Zed will be signed in as a different user specified in + either \`.admins.json\` or \`.admins.default.json\`. + OPTIONS --help Print this help message --release Build Zed in release mode @@ -12,6 +17,16 @@ OPTIONS `.trim(); const { spawn, execFileSync } = require("child_process"); +const assert = require("assert"); + +const defaultUsers = require("../crates/collab/.admins.default.json"); +let users = defaultUsers; +try { + const customUsers = require("../crates/collab/.admins.json"); + assert(customUsers.length > 0); + assert(customUsers.every((user) => typeof user === "string")); + users.splice(0, 0, ...customUsers); +} catch (_) {} const RESOLUTION_REGEX = /(\d+) x (\d+)/; const DIGIT_FLAG_REGEX = /^--?(\d+)$/; @@ -71,10 +86,6 @@ if (instanceCount > 1) { } } -let users = ["nathansobo", "as-cii", "maxbrunsfeld", "iamnbutler"]; - -const RUST_LOG = process.env.RUST_LOG || "info"; - // If a user is specified, make sure it's first in the list const user = process.env.ZED_IMPERSONATE; if (user) { @@ -88,18 +99,12 @@ const positions = [ `${instanceWidth},${instanceHeight}`, ]; -const buildArgs = (() => { - const buildArgs = ["build"]; - if (isReleaseMode) { - buildArgs.push("--release"); - } - - return buildArgs; -})(); -const zedBinary = (() => { - const target = isReleaseMode ? "release" : "debug"; - return `target/${target}/Zed`; -})(); +let buildArgs = ["build"]; +let zedBinary = "target/debug/Zed"; +if (isReleaseMode) { + buildArgs.push("--release"); + zedBinary = "target/release/Zed"; +} execFileSync("cargo", buildArgs, { stdio: "inherit" }); setTimeout(() => { @@ -115,7 +120,7 @@ setTimeout(() => { ZED_ADMIN_API_TOKEN: "secret", ZED_WINDOW_SIZE: `${instanceWidth},${instanceHeight}`, PATH: process.env.PATH, - RUST_LOG, + RUST_LOG: process.env.RUST_LOG || "info", }, }); } From ad2b4f288e493d0f3564cd220c7055776c88298e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 13:24:55 -0800 Subject: [PATCH 274/334] Update procfile and local development docs, zed.dev is no longer needed --- Procfile | 2 - docs/src/SUMMARY.md | 3 + docs/src/developing_zed__building_zed.md | 136 +++++------------- .../developing_zed__local_collaboration.md | 52 +++++-- 4 files changed, 79 insertions(+), 114 deletions(-) diff --git a/Procfile b/Procfile index 3f42c3a9677477bd7dcd04ca61a749206559be40..7bd9114dad4ec3e89c4699d0924bbf1ef1243867 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,2 @@ -web: cd ../zed.dev && PORT=3000 npm run dev collab: cd crates/collab && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve livekit: livekit-server --dev -postgrest: postgrest crates/collab/admin_api.conf diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index ad1cd6332c4a5200a66ff5767db3673abc88f921..e300e9906956c9eb44fe730de93f4de4a8eea90a 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -4,15 +4,18 @@ [Feedback](./feedback.md) # Configuring Zed + - [Settings](./configuring_zed.md) - [Vim Mode](./configuring_zed__configuring_vim.md) # Using Zed + - [Workflows]() - [Collaboration]() - [Using AI]() # Contributing to Zed + - [How to Contribute]() - [Building from Source](./developing_zed__building_zed.md) - [Local Collaboration](./developing_zed__local_collaboration.md) diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index a5270e2b2abc8f89e88e23d016e2e3a21a065efd..7535ceb4d0193e01b46e1cf1f9e2c818c086f138 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -1,133 +1,73 @@ # Building Zed -🚧 TODO: +## Dependencies -- [ ] Remove ZI-specific things -- [ ] Rework any steps that currently require a ZI-specific account +- Install [Rust](https://www.rust-lang.org/tools/install) +- Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store -How to build Zed from source for the first time. +- Install [Xcode command line tools](https://developer.apple.com/xcode/resources/) -### Prerequisites + ```bash + xcode-select --install + ``` -🚧 TODO 🚧 Update for open source +- Ensure that the Xcode command line tools are using your newly installed copy of Xcode: -- Be added to the GitHub organization -- Be added to the Vercel team -- Create a [Personal Access Token](https://github.com/settings/personal-access-tokens/new) on Github - - 🚧 TODO 🚧 What permissions are required? - - 🚧 TODO 🚧 What changes when repo isn't private? - - Go to https://github.com/settings/tokens and Generate new token - - GitHub currently provides two kinds of tokens: - - Classic Tokens, where only `repo` (Full control of private repositories) OAuth scope has to be selected - Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories - - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - - Keep the token in the browser tab/editor for the next two steps + ``` + sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer. + ``` -### Dependencies +* Install the Rust wasm toolchain: -- Install [Rust](https://www.rust-lang.org/tools/install) + ```bash + rustup target add wasm32-wasi + ``` -- Install the [GitHub CLI](https://cli.github.com/), [Livekit](https://formulae.brew.sh/formula/livekit) & [Foreman](https://formulae.brew.sh/formula/foreman) +## Backend Dependencies -```bash -brew install gh -brew install livekit -brew install foreman -``` +If you are developing collaborative features of Zed, you'll need to install the dependencies of zed's `collab` server: -- Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store +- Install [Postgres](https://postgresapp.com) +- Install [Livekit](https://formulae.brew.sh/formula/livekit) and [Foreman](https://formulae.brew.sh/formula/foreman) -- Install [Xcode command line tools](https://developer.apple.com/xcode/resources/) + ```bash + brew install livekit foreman + ``` -```bash -xcode-select --install -``` - -- If `xcode-select --print-path prints /Library/Developer/CommandLineTools…` run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.` +## Building Zed from Source -* Install [Postgres](https://postgresapp.com) +Once you have the dependencies installed, you can build Zed using [Cargo](https://doc.rust-lang.org/cargo/). -* Install the wasm toolchain +For a debug build: -```bash -rustup target add wasm32-wasi ``` - -### Building Zed from Source - -1. Clone the `zed` repo - -```bash -gh repo clone zed-industries/zed +cargo run ``` -1. (Optional but recommended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` -1. (🚧 TODO 🚧 - Will this be relevant for open source?) Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: +For a release build: -```bash -cd .. -git clone https://github.com/zed-industries/zed.dev -cd zed.dev && npm install -pnpm install -g vercel +``` +cargo run --release ``` -1. (🚧 TODO 🚧 - Will this be relevant for open source?) Link your zed.dev project to Vercel - -- `vercel link` -- Select the `zed-industries` team. If you don't have this get someone on the team to add you to it. -- Select the `zed.dev` project - -1. (🚧 TODO 🚧 - Will this be relevant for open source?) Run `vercel pull` to pull down the environment variables and project info from Vercel -1. Open Postgres.app -1. From `./path/to/zed/` run `GITHUB_TOKEN={yourGithubAPIToken} script/bootstrap` - -- You don't need to include the GITHUB_TOKEN if you exported it above. -- Consider removing the token (if it's fine for you to recreate such tokens during occasional migrations) or store this token somewhere safe (like your Zed 1Password vault). +And to run the tests: -1. To run the Zed app: - - If you are working on zed: - - `cargo run` - - If you are just using the latest version, but not working on zed: - - `cargo run --release` - - If you need to run the collaboration server locally: - - `script/zed-local` +``` +cargo test --workspace +``` ## Troubleshooting -**`error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)`** - -- Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer` - -**`xcrun: error: unable to find utility "metal", not a developer tool or in PATH`** +### Error compiling metal shaders -### `script/bootstrap` - -```bash -Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)! -Please create a new installation in /opt/homebrew using one of the -"Alternative Installs" from: -https://docs.brew.sh/Installation ``` +error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)`** -- In that case try `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` - -- If Homebrew is not in your PATH: - - Replace `{username}` with your home folder name (usually your login name) - - `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/{username}/.zprofile` - - `eval "$(/opt/homebrew/bin/brew shellenv)"` - +xcrun: error: unable to find utility "metal", not a developer tool or in PATH ``` -seeding database... -thread 'main' panicked at 'failed to deserialize github user from 'https://api.github.com/orgs/zed-industries/teams/staff/members': reqwest::Error { kind: Decode, source: Error("invalid type: map, expected a sequence", line: 1, column: 0) }', crates/collab/src/bin/seed.rs:111:10 -``` - -Wrong permissions for `GITHUB_TOKEN` token used, the token needs to be able to read from private repos. -For Classic GitHub Tokens, that required OAuth scope `repo` (seacrh the scope name above for more details) - -Same command -`sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer` +Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer` -### If you experience errors that mention some dependency is using unstable features +### Cargo errors claiming that a dependency is using unstable features Try `cargo clean` and `cargo build`, diff --git a/docs/src/developing_zed__local_collaboration.md b/docs/src/developing_zed__local_collaboration.md index 7bbbda36457174816ccb0da6d98dac8543b5f065..0fc08ef767df89ab9c059f30dbbc4cb95c227129 100644 --- a/docs/src/developing_zed__local_collaboration.md +++ b/docs/src/developing_zed__local_collaboration.md @@ -1,22 +1,46 @@ # Local Collaboration -## Setting up the local collaboration server +First, make sure you've installed Zed's [backend dependencies](/developing_zed__building_zed.html#backend-dependencies). -### Setting up for the first time? +## Database setup -1. Make sure you have livekit installed (`brew install livekit`) -1. Install [Postgres](https://postgresapp.com) and run it. -1. Then, from the root of the repo, run `script/bootstrap`. +Before you can run the `collab` server locally, you'll need to set up a `zed` Postgres database. -### Have a db that is out of date? / Need to migrate? +``` +script/bootstrap +``` -1. Make sure you have livekit installed (`brew install livekit`) -1. Try `cd crates/collab && cargo run -- migrate` from the root of the repo. -1. Run `script/seed-db` +This script will set up the `zed` Postgres database, and populate it with some users. It requires internet access, because it fetches some users from the GitHub API. -## Testing collab locally +The script will create several *admin* users, who you'll sign in as by default when developing locally. The GitHub logins for these default admin users are specified in this file: -1. Run `foreman start` from the root of the repo. -1. In another terminal run `script/zed-local`. -1. Two copies of Zed will open. Add yourself as a contact in the one that is not you. -1. Start a collaboration session as normal with any open project. +``` +cat crates/collab/.admins.default.json +``` + +To use a different set of admin users, you can create a file called `.admins.json` in the same directory: + +``` +cat > crates/collab/.admins.json < Date: Wed, 17 Jan 2024 13:00:29 -0800 Subject: [PATCH 275/334] Drop scroll events if there's been a reset co-authored-by: Nathan co-authored-by: Conrad --- crates/gpui/src/app/test_context.rs | 3 +- crates/gpui/src/elements/list.rs | 201 ++++++++++++++-------------- crates/gpui/src/keymap/matcher.rs | 1 - crates/gpui/src/window.rs | 1 - 4 files changed, 102 insertions(+), 104 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 41cb722081b60b9b404244ed0e12e126dd63b279..d95558f058a91bb4a7cd9a3ab347ee10584bffba 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -643,7 +643,8 @@ impl<'a> VisualTestContext { /// Simulate an event from the platform, e.g. a SrollWheelEvent /// Make sure you've called [VisualTestContext::draw] first! pub fn simulate_event(&mut self, event: E) { - self.update(|cx| cx.dispatch_event(event.to_platform_input())); + self.test_window(self.window) + .simulate_input(event.to_platform_input()); self.background_executor.run_until_parked(); } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 9d49149e7abe7333f42abe5eb07b32e697098774..c0874a8dd4116275846edc24c1ebcfcfd9d752b6 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -28,9 +28,9 @@ struct StateInner { render_item: Box AnyElement>, items: SumTree, logical_scroll_top: Option, - pending_scroll_delta: Pixels, alignment: ListAlignment, overdraw: Pixels, + reset: bool, #[allow(clippy::type_complexity)] scroll_handler: Option>, } @@ -93,12 +93,17 @@ impl ListState { alignment: orientation, overdraw, scroll_handler: None, - pending_scroll_delta: px(0.), + reset: false, }))) } + /// Reset this instantiation of the list state. + /// + /// Note that this will cause scroll events to be dropped until the next paint. pub fn reset(&self, element_count: usize) { let state = &mut *self.0.borrow_mut(); + state.reset = true; + state.logical_scroll_top = None; state.items = SumTree::new(); state @@ -154,11 +159,13 @@ impl ListState { scroll_top.item_ix = item_count; scroll_top.offset_in_item = px(0.); } + state.logical_scroll_top = Some(scroll_top); } pub fn scroll_to_reveal_item(&self, ix: usize) { let state = &mut *self.0.borrow_mut(); + let mut scroll_top = state.logical_scroll_top(); let height = state .last_layout_bounds @@ -189,9 +196,9 @@ impl ListState { /// Get the bounds for the given item in window coordinates. pub fn bounds_for_item(&self, ix: usize) -> Option> { let state = &*self.0.borrow(); + let bounds = state.last_layout_bounds.unwrap_or_default(); let scroll_top = state.logical_scroll_top(); - if ix < scroll_top.item_ix { return None; } @@ -232,7 +239,11 @@ impl StateInner { delta: Point, cx: &mut WindowContext, ) { - // self.pending_scroll_delta += delta.y; + // Drop scroll events after a reset, since we can't calculate + // the new logical scroll top without the item heights + if self.reset { + return; + } let scroll_max = (self.items.summary().height - height).max(px(0.)); let new_scroll_top = (self.scroll_top(scroll_top) - delta.y) @@ -329,6 +340,8 @@ impl Element for List { ) { let state = &mut *self.state.0.borrow_mut(); + state.reset = false; + // If the width of the list has changed, invalidate all cached item heights if state.last_layout_bounds.map_or(true, |last_bounds| { last_bounds.size.width != bounds.size.width @@ -352,117 +365,104 @@ impl Element for List { let mut cursor = old_items.cursor::(); - loop { - // Render items after the scroll top, including those in the trailing overdraw - cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); - for (ix, item) in cursor.by_ref().enumerate() { - let visible_height = rendered_height - scroll_top.offset_in_item; - if visible_height >= bounds.size.height + state.overdraw { - break; - } - - // Use the previously cached height if available - let mut height = if let ListItem::Rendered { height } = item { - Some(*height) - } else { - None - }; - - // If we're within the visible area or the height wasn't cached, render and measure the item's element - if visible_height < bounds.size.height || height.is_none() { - let mut element = (state.render_item)(scroll_top.item_ix + ix, cx); - let element_size = element.measure(available_item_space, cx); - height = Some(element_size.height); - if visible_height < bounds.size.height { - item_elements.push_back(element); - } - } - - let height = height.unwrap(); - rendered_height += height; - measured_items.push_back(ListItem::Rendered { height }); + // Render items after the scroll top, including those in the trailing overdraw + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + for (ix, item) in cursor.by_ref().enumerate() { + let visible_height = rendered_height - scroll_top.offset_in_item; + if visible_height >= bounds.size.height + state.overdraw { + break; } - // Prepare to start walking upward from the item at the scroll top. - cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); - - // If the rendered items do not fill the visible region, then adjust - // the scroll top upward. - if rendered_height - scroll_top.offset_in_item < bounds.size.height { - while rendered_height < bounds.size.height { - cursor.prev(&()); - if cursor.item().is_some() { - let mut element = (state.render_item)(cursor.start().0, cx); - let element_size = element.measure(available_item_space, cx); - - rendered_height += element_size.height; - measured_items.push_front(ListItem::Rendered { - height: element_size.height, - }); - item_elements.push_front(element) - } else { - break; - } + // Use the previously cached height if available + let mut height = if let ListItem::Rendered { height } = item { + Some(*height) + } else { + None + }; + + // If we're within the visible area or the height wasn't cached, render and measure the item's element + if visible_height < bounds.size.height || height.is_none() { + let mut element = (state.render_item)(scroll_top.item_ix + ix, cx); + let element_size = element.measure(available_item_space, cx); + height = Some(element_size.height); + if visible_height < bounds.size.height { + item_elements.push_back(element); } + } - scroll_top = ListOffset { - item_ix: cursor.start().0, - offset_in_item: rendered_height - bounds.size.height, - }; + let height = height.unwrap(); + rendered_height += height; + measured_items.push_back(ListItem::Rendered { height }); + } - match state.alignment { - ListAlignment::Top => { - scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.)); - state.logical_scroll_top = Some(scroll_top); - } - ListAlignment::Bottom => { - scroll_top = ListOffset { - item_ix: cursor.start().0, - offset_in_item: rendered_height - bounds.size.height, - }; - state.logical_scroll_top = None; - } - }; - } + // Prepare to start walking upward from the item at the scroll top. + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); - // Measure items in the leading overdraw - let mut leading_overdraw = scroll_top.offset_in_item; - while leading_overdraw < state.overdraw { + // If the rendered items do not fill the visible region, then adjust + // the scroll top upward. + if rendered_height - scroll_top.offset_in_item < bounds.size.height { + while rendered_height < bounds.size.height { cursor.prev(&()); - if let Some(item) = cursor.item() { - let height = if let ListItem::Rendered { height } = item { - *height - } else { - let mut element = (state.render_item)(cursor.start().0, cx); - element.measure(available_item_space, cx).height - }; + if cursor.item().is_some() { + let mut element = (state.render_item)(cursor.start().0, cx); + let element_size = element.measure(available_item_space, cx); - leading_overdraw += height; - measured_items.push_front(ListItem::Rendered { height }); + rendered_height += element_size.height; + measured_items.push_front(ListItem::Rendered { + height: element_size.height, + }); + item_elements.push_front(element) } else { break; } } - let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); - let mut cursor = old_items.cursor::(); - let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); - new_items.extend(measured_items, &()); - cursor.seek(&Count(measured_range.end), Bias::Right, &()); - new_items.append(cursor.suffix(&()), &()); + scroll_top = ListOffset { + item_ix: cursor.start().0, + offset_in_item: rendered_height - bounds.size.height, + }; - state.items = new_items; - state.last_layout_bounds = Some(bounds); + match state.alignment { + ListAlignment::Top => { + scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.)); + state.logical_scroll_top = Some(scroll_top); + } + ListAlignment::Bottom => { + scroll_top = ListOffset { + item_ix: cursor.start().0, + offset_in_item: rendered_height - bounds.size.height, + }; + state.logical_scroll_top = None; + } + }; + } - // if !state.pending_scroll_delta.is_zero() { - // // Do scroll manipulation + // Measure items in the leading overdraw + let mut leading_overdraw = scroll_top.offset_in_item; + while leading_overdraw < state.overdraw { + cursor.prev(&()); + if let Some(item) = cursor.item() { + let height = if let ListItem::Rendered { height } = item { + *height + } else { + let mut element = (state.render_item)(cursor.start().0, cx); + element.measure(available_item_space, cx).height + }; - // state.pending_scroll_delta = px(0.); - // } else { - break; - // } + leading_overdraw += height; + measured_items.push_front(ListItem::Rendered { height }); + } else { + break; + } } + let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); + let mut cursor = old_items.cursor::(); + let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); + new_items.extend(measured_items, &()); + cursor.seek(&Count(measured_range.end), Bias::Right, &()); + new_items.append(cursor.suffix(&()), &()); + // Paint the visible items cx.with_content_mask(Some(ContentMask { bounds }), |cx| { let mut item_origin = bounds.origin; @@ -474,12 +474,13 @@ impl Element for List { } }); + state.items = new_items; + state.last_layout_bounds = Some(bounds); + let list_state = self.state.clone(); let height = bounds.size.height; - dbg!("scroll is being bound"); cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - dbg!("scroll dispatched!"); if phase == DispatchPhase::Bubble && bounds.contains(&event.position) && cx.was_top_layer(&event.position, cx.stacking_order()) @@ -624,7 +625,5 @@ mod test { // Scroll position should stay at the top of the list assert_eq!(state.logical_scroll_top().item_ix, 0); assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); - - panic!("We should not get here yet!") } } diff --git a/crates/gpui/src/keymap/matcher.rs b/crates/gpui/src/keymap/matcher.rs index 5410ddce06e9ca999aaee0911fd7a69d6a0e61c8..491dee6895fe391a4b93233cf6b861875deac803 100644 --- a/crates/gpui/src/keymap/matcher.rs +++ b/crates/gpui/src/keymap/matcher.rs @@ -209,7 +209,6 @@ mod tests { ); assert!(!matcher.has_pending_keystrokes()); - eprintln!("PROBLEM AREA"); // If a is prefixed, C will not be dispatched because there // was a pending binding for it assert_eq!( diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 00b17ba3c07ba6db532035c37d5bf7cdd74e9f59..11a720bf1f17aab827a581fff5f3a4b1b63b8379 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1717,7 +1717,6 @@ impl<'a> WindowContext<'a> { .mouse_listeners .remove(&event.type_id()) { - dbg!(handlers.len()); // Because handlers may add other handlers, we sort every time. handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); From 8f3d79c3b811fdaa9ae6e85799006f43c7c94ae9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Jan 2024 16:59:57 -0500 Subject: [PATCH 276/334] Fix file paths in stories (#4104) This PR fixes some file paths used in our stories that were still referencing the `ui2` crate. Release Notes: - N/A --- crates/ui/src/components/stories/icon_button.rs | 2 +- crates/ui/src/components/stories/toggle_button.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ui/src/components/stories/icon_button.rs b/crates/ui/src/components/stories/icon_button.rs index ba3d5fd8660988298a38307cb8a690d1a365c7f4..4b53a2c0f230587fcce32edf5de734a41396f579 100644 --- a/crates/ui/src/components/stories/icon_button.rs +++ b/crates/ui/src/components/stories/icon_button.rs @@ -113,7 +113,7 @@ impl Render for IconButtonStory { StoryContainer::new( "Icon Button", - "crates/ui2/src/components/stories/icon_button.rs", + "crates/ui/src/components/stories/icon_button.rs", ) .child(StorySection::new().children(buttons)) .child( diff --git a/crates/ui/src/components/stories/toggle_button.rs b/crates/ui/src/components/stories/toggle_button.rs index da2a2512c44a7705d817a12a25b6355f7f792c04..68789a53409987c9582dd6b42027b342d1a93c97 100644 --- a/crates/ui/src/components/stories/toggle_button.rs +++ b/crates/ui/src/components/stories/toggle_button.rs @@ -9,7 +9,7 @@ impl Render for ToggleButtonStory { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { StoryContainer::new( "Toggle Button", - "crates/ui2/src/components/stories/toggle_button.rs", + "crates/ui/src/components/stories/toggle_button.rs", ) .child( StorySection::new().child( From d67e461325d530a3da8befb72670f998612cd16a Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 10:00:21 -0800 Subject: [PATCH 277/334] =?UTF-8?q?document=20app=20module=20in=20gpui=20?= =?UTF-8?q?=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit co-authored-by: Nathan --- crates/gpui/src/app.rs | 65 +++++++++++++++++++++++++++- crates/gpui/src/app/async_context.rs | 31 +++++++++++-- crates/gpui/src/app/entity_map.rs | 25 +++++++++-- crates/gpui/src/app/model_context.rs | 17 ++++++++ 4 files changed, 128 insertions(+), 10 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 41519f0ae4d3623a5b3e57c06a265c5f0a754b23..ab9b4d9f86417c13127de5a8758297c8d4ce0028 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + mod async_context; mod entity_map; mod model_context; @@ -43,6 +45,9 @@ use util::{ ResultExt, }; +/// The duration for which futures returned from [AppContext::on_app_context] or [ModelContext::on_app_quit] can run before the application fully quits. +pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100); + /// Temporary(?) wrapper around [`RefCell`] to help us debug any double borrows. /// Strongly consider removing after stabilization. #[doc(hidden)] @@ -187,6 +192,9 @@ type QuitHandler = Box LocalBoxFuture<'static, () type ReleaseListener = Box; type NewViewListener = Box; +/// Contains the state of the full application, and passed as a reference to a variety of callbacks. +/// Other contexts such as [ModelContext], [WindowContext], and [ViewContext] deref to this type, making it the most general context type. +/// You need a reference to an `AppContext` to access the state of a [Model]. pub struct AppContext { pub(crate) this: Weak, pub(crate) platform: Rc, @@ -312,7 +320,7 @@ impl AppContext { let futures = futures::future::join_all(futures); if self .background_executor - .block_with_timeout(Duration::from_millis(100), futures) + .block_with_timeout(SHUTDOWN_TIMEOUT, futures) .is_err() { log::error!("timed out waiting on app_will_quit"); @@ -446,6 +454,7 @@ impl AppContext { .collect() } + /// Returns a handle to the window that is currently focused at the platform level, if one exists. pub fn active_window(&self) -> Option { self.platform.active_window() } @@ -474,14 +483,17 @@ impl AppContext { self.platform.activate(ignoring_other_apps); } + /// Hide the application at the platform level. pub fn hide(&self) { self.platform.hide(); } + /// Hide other applications at the platform level. pub fn hide_other_apps(&self) { self.platform.hide_other_apps(); } + /// Unhide other applications at the platform level. pub fn unhide_other_apps(&self) { self.platform.unhide_other_apps(); } @@ -521,18 +533,25 @@ impl AppContext { self.platform.open_url(url); } + /// Returns the full pathname of the current app bundle. + /// If the app is not being run from a bundle, returns an error. pub fn app_path(&self) -> Result { self.platform.app_path() } + /// Returns the file URL of the executable with the specified name in the application bundle pub fn path_for_auxiliary_executable(&self, name: &str) -> Result { self.platform.path_for_auxiliary_executable(name) } + /// Returns the maximum duration in which a second mouse click must occur for an event to be a double-click event. pub fn double_click_interval(&self) -> Duration { self.platform.double_click_interval() } + /// Displays a platform modal for selecting paths. + /// When one or more paths are selected, they'll be relayed asynchronously via the returned oneshot channel. + /// If cancelled, a `None` will be relayed instead. pub fn prompt_for_paths( &self, options: PathPromptOptions, @@ -540,22 +559,30 @@ impl AppContext { self.platform.prompt_for_paths(options) } + /// Displays a platform modal for selecting a new path where a file can be saved. + /// The provided directory will be used to set the iniital location. + /// When a path is selected, it is relayed asynchronously via the returned oneshot channel. + /// If cancelled, a `None` will be relayed instead. pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver> { self.platform.prompt_for_new_path(directory) } + /// Reveals the specified path at the platform level, such as in Finder on macOS. pub fn reveal_path(&self, path: &Path) { self.platform.reveal_path(path) } + /// Returns whether the user has configured scrollbars to auto-hide at the platform level. pub fn should_auto_hide_scrollbars(&self) -> bool { self.platform.should_auto_hide_scrollbars() } + /// Restart the application. pub fn restart(&self) { self.platform.restart() } + /// Returns the local timezone at the platform level. pub fn local_timezone(&self) -> UtcOffset { self.platform.local_timezone() } @@ -745,7 +772,7 @@ impl AppContext { } /// Spawns the future returned by the given function on the thread pool. The closure will be invoked - /// with AsyncAppContext, which allows the application state to be accessed across await points. + /// with [AsyncAppContext], which allows the application state to be accessed across await points. pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task where Fut: Future + 'static, @@ -896,6 +923,8 @@ impl AppContext { self.globals_by_type.insert(global_type, lease.global); } + /// Arrange for the given function to be invoked whenever a view of the specified type is created. + /// The function will be passed a mutable reference to the view along with an appropriate context. pub fn observe_new_views( &mut self, on_new: impl 'static + Fn(&mut V, &mut ViewContext), @@ -915,6 +944,8 @@ impl AppContext { subscription } + /// Observe the release of a model or view. The callback is invoked after the model or view + /// has no more strong references but before it has been dropped. pub fn observe_release( &mut self, handle: &E, @@ -935,6 +966,9 @@ impl AppContext { subscription } + /// Register a callback to be invoked when a keystroke is received by the application + /// in any window. Note that this fires after all other action and event mechansims have resolved + /// and that this API will not be invoked if the event's propogation is stopped. pub fn observe_keystrokes( &mut self, f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static, @@ -958,6 +992,7 @@ impl AppContext { self.pending_effects.push_back(Effect::Refresh); } + /// Clear all key bindings in the app. pub fn clear_key_bindings(&mut self) { self.keymap.lock().clear(); self.pending_effects.push_back(Effect::Refresh); @@ -992,6 +1027,7 @@ impl AppContext { self.propagate_event = true; } + /// Build an action from some arbitrary data, typically a keymap entry. pub fn build_action( &self, name: &str, @@ -1000,10 +1036,16 @@ impl AppContext { self.actions.build_action(name, data) } + /// Get a list of all action names that have been registered. + /// in the application. Note that registration only allows for + /// actions to be built dynamically, and is unrelated to binding + /// actions in the element tree. pub fn all_action_names(&self) -> &[SharedString] { self.actions.all_action_names() } + /// Register a callback to be invoked when the application is about to quit. + /// It is not possible to cancel the quit event at this point. pub fn on_app_quit( &mut self, mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static, @@ -1039,6 +1081,8 @@ impl AppContext { } } + /// Checks if the given action is bound in the current context, as defined by the app's current focus, + /// the bindings in the element tree, and any global action listeners. pub fn is_action_available(&mut self, action: &dyn Action) -> bool { if let Some(window) = self.active_window() { if let Ok(window_action_available) = @@ -1052,10 +1096,13 @@ impl AppContext { .contains_key(&action.as_any().type_id()) } + /// Set the menu bar for this application. This will replace any existing menu bar. pub fn set_menus(&mut self, menus: Vec) { self.platform.set_menus(menus, &self.keymap.lock()); } + /// Dispatch an action to the currently active window or global action handler + /// See [action::Action] for more information on how actions work pub fn dispatch_action(&mut self, action: &dyn Action) { if let Some(active_window) = self.active_window() { active_window @@ -1110,6 +1157,7 @@ impl AppContext { } } + /// Is there currently something being dragged? pub fn has_active_drag(&self) -> bool { self.active_drag.is_some() } @@ -1262,8 +1310,14 @@ impl DerefMut for GlobalLease { /// Contains state associated with an active drag operation, started by dragging an element /// within the window or by dragging into the app from the underlying platform. pub struct AnyDrag { + /// The view used to render this drag pub view: AnyView, + + /// The value of the dragged item, to be dropped pub value: Box, + + /// This is used to render the dragged item in the same place + /// on the original element that the drag was initiated pub cursor_offset: Point, } @@ -1271,12 +1325,19 @@ pub struct AnyDrag { /// tooltip behavior on a custom element. Otherwise, use [Div::tooltip]. #[derive(Clone)] pub struct AnyTooltip { + /// The view used to display the tooltip pub view: AnyView, + + /// The offset from the cursor to use, relative to the parent view pub cursor_offset: Point, } +/// A keystroke event, and potentially the associated action #[derive(Debug)] pub struct KeystrokeEvent { + /// The keystroke that occurred pub keystroke: Keystroke, + + /// The action that was resolved for the keystroke, if any pub action: Option>, } diff --git a/crates/gpui/src/app/async_context.rs b/crates/gpui/src/app/async_context.rs index 6afb356e5e63c6431fe0858b4e1c4fd97159c0b2..1ee01d90dfac22632f718088bbd7bbe54364136c 100644 --- a/crates/gpui/src/app/async_context.rs +++ b/crates/gpui/src/app/async_context.rs @@ -7,6 +7,9 @@ use anyhow::{anyhow, Context as _}; use derive_more::{Deref, DerefMut}; use std::{future::Future, rc::Weak}; +/// An async-friendly version of [AppContext] with a static lifetime so it can be held across `await` points in async code. +/// You're provided with an instance when calling [AppContext::spawn], and you can also create one with [AppContext::to_async]. +/// Internally, this holds a weak reference to an `AppContext`, so its methods are fallible to protect against cases where the [AppContext] is dropped. #[derive(Clone)] pub struct AsyncAppContext { pub(crate) app: Weak, @@ -139,6 +142,8 @@ impl AsyncAppContext { self.foreground_executor.spawn(f(self.clone())) } + /// Determine whether global state of the specified type has been assigned. + /// Returns an error if the `AppContext` has been dropped. pub fn has_global(&self) -> Result { let app = self .app @@ -148,6 +153,9 @@ impl AsyncAppContext { Ok(app.has_global::()) } + /// Reads the global state of the specified type, passing it to the given callback. + /// Panics if no global state of the specified type has been assigned. + /// Returns an error if the `AppContext` has been dropped. pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result { let app = self .app @@ -157,6 +165,9 @@ impl AsyncAppContext { Ok(read(app.global(), &app)) } + /// Reads the global state of the specified type, passing it to the given callback. + /// Similar to [read_global], but returns an error instead of panicking if no state of the specified type has been assigned. + /// Returns an error if no state of the specified type has been assigned the `AppContext` has been dropped. pub fn try_read_global( &self, read: impl FnOnce(&G, &AppContext) -> R, @@ -166,6 +177,8 @@ impl AsyncAppContext { Some(read(app.try_global()?, &app)) } + /// A convenience method for [AppContext::update_global] + /// for updating the global state of the specified type. pub fn update_global( &mut self, update: impl FnOnce(&mut G, &mut AppContext) -> R, @@ -179,6 +192,8 @@ impl AsyncAppContext { } } +/// A cloneable, owned handle to the application context, +/// composed with the window associated with the current task. #[derive(Clone, Deref, DerefMut)] pub struct AsyncWindowContext { #[deref] @@ -188,14 +203,16 @@ pub struct AsyncWindowContext { } impl AsyncWindowContext { - pub fn window_handle(&self) -> AnyWindowHandle { - self.window - } - pub(crate) fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self { Self { app, window } } + /// Get the handle of the window this context is associated with. + pub fn window_handle(&self) -> AnyWindowHandle { + self.window + } + + /// A convenience method for [WindowContext::update()] pub fn update( &mut self, update: impl FnOnce(AnyView, &mut WindowContext) -> R, @@ -203,10 +220,12 @@ impl AsyncWindowContext { self.app.update_window(self.window, update) } + /// A convenience method for [WindowContext::on_next_frame()] pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) { self.window.update(self, |_, cx| cx.on_next_frame(f)).ok(); } + /// A convenience method for [AppContext::global()] pub fn read_global( &mut self, read: impl FnOnce(&G, &WindowContext) -> R, @@ -214,6 +233,8 @@ impl AsyncWindowContext { self.window.update(self, |_, cx| read(cx.global(), cx)) } + /// A convenience method for [AppContext::update_global()] + /// for updating the global state of the specified type. pub fn update_global( &mut self, update: impl FnOnce(&mut G, &mut WindowContext) -> R, @@ -224,6 +245,8 @@ impl AsyncWindowContext { self.window.update(self, |_, cx| cx.update_global(update)) } + /// Schedule a future to be executed on the main thread. This is used for collecting + /// the results of background tasks and updating the UI. pub fn spawn(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task where Fut: Future + 'static, diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 1e593caf98a34f64939b6253fbb1eccd0244bfb3..d3f8a7ea894991822cc9764627f4a77bb0fa28f6 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -31,6 +31,7 @@ impl From for EntityId { } impl EntityId { + /// Converts this entity id to a [u64] pub fn as_u64(self) -> u64 { self.0.as_ffi() } @@ -140,7 +141,7 @@ impl EntityMap { } } -pub struct Lease<'a, T> { +pub(crate) struct Lease<'a, T> { entity: Option>, pub model: &'a Model, entity_type: PhantomData, @@ -169,8 +170,9 @@ impl<'a, T> Drop for Lease<'a, T> { } #[derive(Deref, DerefMut)] -pub struct Slot(Model); +pub(crate) struct Slot(Model); +/// A dynamically typed reference to a model, which can be downcast into a `Model`. pub struct AnyModel { pub(crate) entity_id: EntityId, pub(crate) entity_type: TypeId, @@ -195,14 +197,17 @@ impl AnyModel { } } + /// Returns the id associated with this model. pub fn entity_id(&self) -> EntityId { self.entity_id } + /// Returns the [TypeId] associated with this model. pub fn entity_type(&self) -> TypeId { self.entity_type } + /// Converts this model handle into a weak variant, which does not prevent it from being released. pub fn downgrade(&self) -> AnyWeakModel { AnyWeakModel { entity_id: self.entity_id, @@ -211,6 +216,8 @@ impl AnyModel { } } + /// Converts this model handle into a strongly-typed model handle of the given type. + /// If this model handle is not of the specified type, returns itself as an error variant. pub fn downcast(self) -> Result, AnyModel> { if TypeId::of::() == self.entity_type { Ok(Model { @@ -307,6 +314,8 @@ impl std::fmt::Debug for AnyModel { } } +/// A strong, well typed reference to a struct which is managed +/// by GPUI #[derive(Deref, DerefMut)] pub struct Model { #[deref] @@ -368,10 +377,12 @@ impl Model { self.any_model } + /// Grab a reference to this entity from the context. pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T { cx.entities.read(self) } + /// Read the entity referenced by this model with the given function. pub fn read_with( &self, cx: &C, @@ -437,6 +448,7 @@ impl PartialEq> for Model { } } +/// A type erased, weak reference to a model. #[derive(Clone)] pub struct AnyWeakModel { pub(crate) entity_id: EntityId, @@ -445,10 +457,12 @@ pub struct AnyWeakModel { } impl AnyWeakModel { + /// Get the entity ID associated with this weak reference. pub fn entity_id(&self) -> EntityId { self.entity_id } + /// Check if this weak handle can be upgraded, or if the model has already been dropped pub fn is_upgradable(&self) -> bool { let ref_count = self .entity_ref_counts @@ -458,6 +472,7 @@ impl AnyWeakModel { ref_count > 0 } + /// Upgrade this weak model reference to a strong reference. pub fn upgrade(&self) -> Option { let ref_counts = &self.entity_ref_counts.upgrade()?; let ref_counts = ref_counts.read(); @@ -485,6 +500,7 @@ impl AnyWeakModel { }) } + /// Assert that model referenced by this weak handle has been dropped. #[cfg(any(test, feature = "test-support"))] pub fn assert_dropped(&self) { self.entity_ref_counts @@ -527,6 +543,7 @@ impl PartialEq for AnyWeakModel { impl Eq for AnyWeakModel {} +/// A weak reference to a model of the given type. #[derive(Deref, DerefMut)] pub struct WeakModel { #[deref] @@ -617,12 +634,12 @@ lazy_static::lazy_static! { #[cfg(any(test, feature = "test-support"))] #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] -pub struct HandleId { +pub(crate) struct HandleId { id: u64, // id of the handle itself, not the pointed at object } #[cfg(any(test, feature = "test-support"))] -pub struct LeakDetector { +pub(crate) struct LeakDetector { next_handle_id: u64, entity_handles: HashMap>>, } diff --git a/crates/gpui/src/app/model_context.rs b/crates/gpui/src/app/model_context.rs index 63db0ee1cb1c2e28a11268e5d1df8b74189954b3..e2aad9fee93aeba5c0c022a5d619f8cdd57b0e63 100644 --- a/crates/gpui/src/app/model_context.rs +++ b/crates/gpui/src/app/model_context.rs @@ -11,6 +11,7 @@ use std::{ future::Future, }; +/// The app context, with specialized behavior for the given model. #[derive(Deref, DerefMut)] pub struct ModelContext<'a, T> { #[deref] @@ -24,20 +25,24 @@ impl<'a, T: 'static> ModelContext<'a, T> { Self { app, model_state } } + /// The entity id of the model backing this context. pub fn entity_id(&self) -> EntityId { self.model_state.entity_id } + /// Returns a handle to the model belonging to this context. pub fn handle(&self) -> Model { self.weak_model() .upgrade() .expect("The entity must be alive if we have a model context") } + /// Returns a weak handle to the model belonging to this context. pub fn weak_model(&self) -> WeakModel { self.model_state.clone() } + /// Arranges for the given function to be called whenever [ModelContext::notify] or [ViewContext::notify] is called with the given model or view. pub fn observe( &mut self, entity: &E, @@ -59,6 +64,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { }) } + /// Subscribe to an event type from another model or view pub fn subscribe( &mut self, entity: &E, @@ -81,6 +87,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { }) } + /// Register a callback to be invoked when GPUI releases this model. pub fn on_release( &mut self, on_release: impl FnOnce(&mut T, &mut AppContext) + 'static, @@ -99,6 +106,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { subscription } + /// Register a callback to be run on the release of another model or view pub fn observe_release( &mut self, entity: &E, @@ -124,6 +132,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { subscription } + /// Register a callback to for updates to the given global pub fn observe_global( &mut self, mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + 'static, @@ -140,6 +149,8 @@ impl<'a, T: 'static> ModelContext<'a, T> { subscription } + /// Arrange for the given function to be invoked whenever the application is quit. + /// The future returned from this callback will be polled for up to [gpui::SHUTDOWN_TIMEOUT] until the app fully quits. pub fn on_app_quit( &mut self, mut on_quit: impl FnMut(&mut T, &mut ModelContext) -> Fut + 'static, @@ -165,6 +176,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { subscription } + /// Tell GPUI that this model has changed and observers of it should be notified. pub fn notify(&mut self) { if self .app @@ -177,6 +189,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { } } + /// Update the given global pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R where G: 'static, @@ -187,6 +200,9 @@ impl<'a, T: 'static> ModelContext<'a, T> { result } + /// Spawn the future returned by the given function. + /// The function is provided a weak handle to the model owned by this context and a context that can be held across await points. + /// The returned task must be held or detached. pub fn spawn(&self, f: impl FnOnce(WeakModel, AsyncAppContext) -> Fut) -> Task where T: 'static, @@ -199,6 +215,7 @@ impl<'a, T: 'static> ModelContext<'a, T> { } impl<'a, T> ModelContext<'a, T> { + /// Emit an event of the specified type, which can be handled by other entities that have subscribed via `subscribe` methods on their respective contexts. pub fn emit(&mut self, event: Evt) where T: EventEmitter, From 7a299e966afeb9eb5f0940ad9f22cdfcaa886a4c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 10:21:29 -0800 Subject: [PATCH 278/334] Document view crate co-authored-by: Nathan --- crates/gpui/src/view.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 968fbbd94cd142bdfc6629539da997652f71d91c..3701bbbd69419daff04e83fdc85413099d34f42f 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -1,3 +1,5 @@ +#![deny(missing_docs)] + use crate::{ seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, @@ -11,12 +13,16 @@ use std::{ hash::{Hash, Hasher}, }; +/// A view is a piece of state that can be presented on screen by implementing the [Render] trait. +/// Views implement [Element] and can composed with other views, and every window is created with a root view. pub struct View { + /// A view is just a [Model] whose type implements `Render`, and the model is accessible via this field. pub model: Model, } impl Sealed for View {} +#[doc(hidden)] pub struct AnyViewState { root_style: Style, cache_key: Option, @@ -58,6 +64,7 @@ impl View { Entity::downgrade(self) } + /// Update the view's state with the given function, which is passed a mutable reference and a context. pub fn update( &self, cx: &mut C, @@ -69,10 +76,12 @@ impl View { cx.update_view(self, f) } + /// Obtain a read-only reference to this view's state. pub fn read<'a>(&self, cx: &'a AppContext) -> &'a V { self.model.read(cx) } + /// Gets a [FocusHandle] for this view when its state implements [FocusableView]. pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle where V: FocusableView, @@ -131,19 +140,24 @@ impl PartialEq for View { impl Eq for View {} +/// A weak variant of [View] which does not prevent the view from being released. pub struct WeakView { pub(crate) model: WeakModel, } impl WeakView { + /// Gets the entity id associated with this handle. pub fn entity_id(&self) -> EntityId { self.model.entity_id } + /// Obtain a strong handle for the view if it hasn't been released. pub fn upgrade(&self) -> Option> { Entity::upgrade_from(self) } + /// Update this view's state if it hasn't been released. + /// Returns an error if this view has been released. pub fn update( &self, cx: &mut C, @@ -157,9 +171,10 @@ impl WeakView { Ok(view.update(cx, f)).flatten() } + /// Assert that the view referenced by this handle has been released. #[cfg(any(test, feature = "test-support"))] - pub fn assert_dropped(&self) { - self.model.assert_dropped() + pub fn assert_released(&self) { + self.model.assert_released() } } @@ -185,6 +200,7 @@ impl PartialEq for WeakView { impl Eq for WeakView {} +/// A dynically-typed handle to a view, which can be downcast to a [View] for a specific type. #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, @@ -193,11 +209,15 @@ pub struct AnyView { } impl AnyView { + /// Indicate that this view should be cached when using it as an element. + /// When using this method, the view's previous layout and paint will be recycled from the previous frame if [ViewContext::notify] has not been called since it was rendered. + /// The one exception is when [WindowContext::refresh] is called, in which case caching is ignored. pub fn cached(mut self) -> Self { self.cache = true; self } + /// Convert this to a weak handle. pub fn downgrade(&self) -> AnyWeakView { AnyWeakView { model: self.model.downgrade(), @@ -205,6 +225,8 @@ impl AnyView { } } + /// Convert this to a [View] of a specific type. + /// If this handle does not contain a view of the specified type, returns itself in an `Err` variant. pub fn downcast(self) -> Result, Self> { match self.model.downcast() { Ok(model) => Ok(View { model }), @@ -216,10 +238,12 @@ impl AnyView { } } + /// Gets the [TypeId] of the underlying view. pub fn entity_type(&self) -> TypeId { self.model.entity_type } + /// Gets the entity id of this handle. pub fn entity_id(&self) -> EntityId { self.model.entity_id() } @@ -337,12 +361,14 @@ impl IntoElement for AnyView { } } +/// A weak, dynamically-typed view handle that does not prevent the view from being released. pub struct AnyWeakView { model: AnyWeakModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), } impl AnyWeakView { + /// Convert to a strongly-typed handle if the referenced view has not yet been released. pub fn upgrade(&self) -> Option { let model = self.model.upgrade()?; Some(AnyView { From 9eecda2dae1633568285adc27d37a2de2408120c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 10:23:46 -0800 Subject: [PATCH 279/334] Update method name and partially document platform crate co-authored-by: Nathan --- crates/collab/src/tests/following_tests.rs | 2 +- crates/gpui/src/app/entity_map.rs | 12 +++++----- crates/gpui/src/executor.rs | 3 ++- crates/gpui/src/platform.rs | 26 ++++++++++++++++----- crates/gpui/src/platform/mac/display.rs | 5 ---- crates/gpui/src/platform/test/display.rs | 4 ---- crates/gpui/src/scene.rs | 8 +++---- crates/gpui/src/text_system.rs | 2 +- crates/gpui/src/text_system/line_wrapper.rs | 2 +- crates/zed/src/zed.rs | 6 ++--- 10 files changed, 38 insertions(+), 32 deletions(-) diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index dc5488ebb335ba0e1ecce9800a7c689d217d5a0e..af184d7d02a3458deddb7bfdbf1f77be48d70790 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -249,7 +249,7 @@ async fn test_basic_following( executor.run_until_parked(); cx_c.cx.update(|_| {}); - weak_workspace_c.assert_dropped(); + weak_workspace_c.assert_released(); // Clients A and B see that client B is following A, and client C is not present in the followers. executor.run_until_parked(); diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index d3f8a7ea894991822cc9764627f4a77bb0fa28f6..7ab21a5477526d064d70283a04442b33cffcedfa 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -281,7 +281,7 @@ impl Drop for AnyModel { entity_map .write() .leak_detector - .handle_dropped(self.entity_id, self.handle_id) + .handle_released(self.entity_id, self.handle_id) } } } @@ -500,15 +500,15 @@ impl AnyWeakModel { }) } - /// Assert that model referenced by this weak handle has been dropped. + /// Assert that model referenced by this weak handle has been released. #[cfg(any(test, feature = "test-support"))] - pub fn assert_dropped(&self) { + pub fn assert_released(&self) { self.entity_ref_counts .upgrade() .unwrap() .write() .leak_detector - .assert_dropped(self.entity_id); + .assert_released(self.entity_id); if self .entity_ref_counts @@ -658,12 +658,12 @@ impl LeakDetector { handle_id } - pub fn handle_dropped(&mut self, entity_id: EntityId, handle_id: HandleId) { + pub fn handle_released(&mut self, entity_id: EntityId, handle_id: HandleId) { let handles = self.entity_handles.entry(entity_id).or_default(); handles.remove(&handle_id); } - pub fn assert_dropped(&mut self, entity_id: EntityId) { + pub fn assert_released(&mut self, entity_id: EntityId) { let handles = self.entity_handles.entry(entity_id).or_default(); if !handles.is_empty() { for (_, backtrace) in handles { diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index fc60cb1ec6afcd1c79f5b561a436eac4635c47bc..8571c1ee57adf49d0b91166a31687b8612d10da3 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -109,9 +109,10 @@ type AnyFuture = Pin>>; /// BackgroundExecutor lets you run things on background threads. /// In production this is a thread pool with no ordering guarantees. -/// In tests this is simalated by running tasks one by one in a deterministic +/// In tests this is simulated by running tasks one by one in a deterministic /// (but arbitrary) order controlled by the `SEED` environment variable. impl BackgroundExecutor { + #[doc(hidden)] pub fn new(dispatcher: Arc) -> Self { Self { dispatcher } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index f165cd9c2b5aa71a3a982a5e23faafaca2b8bf3a..7b260f1a7d1aa56d27e52bdd2d0d30501a5d8c9c 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -114,15 +114,20 @@ pub(crate) trait Platform: 'static { fn delete_credentials(&self, url: &str) -> Result<()>; } +/// A handle to a platform's display, e.g. a monitor or laptop screen. pub trait PlatformDisplay: Send + Sync + Debug { + /// Get the ID for this display fn id(&self) -> DisplayId; + /// Returns a stable identifier for this display that can be persisted and used /// across system restarts. fn uuid(&self) -> Result; - fn as_any(&self) -> &dyn Any; + + /// Get the bounds for this display fn bounds(&self) -> Bounds; } +/// An opaque identifier for a hardware display #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct DisplayId(pub(crate) u32); @@ -134,7 +139,7 @@ impl Debug for DisplayId { unsafe impl Send for DisplayId {} -pub trait PlatformWindow { +pub(crate) trait PlatformWindow { fn bounds(&self) -> WindowBounds; fn content_size(&self) -> Size; fn scale_factor(&self) -> f32; @@ -175,6 +180,9 @@ pub trait PlatformWindow { } } +/// This type is public so that our test macro can generate and use it, but it should not +/// be considered part of our public API. +#[doc(hidden)] pub trait PlatformDispatcher: Send + Sync { fn is_main_thread(&self) -> bool; fn dispatch(&self, runnable: Runnable, label: Option); @@ -190,7 +198,7 @@ pub trait PlatformDispatcher: Send + Sync { } } -pub trait PlatformTextSystem: Send + Sync { +pub(crate) trait PlatformTextSystem: Send + Sync { fn add_fonts(&self, fonts: &[Arc>]) -> Result<()>; fn all_font_names(&self) -> Vec; fn font_id(&self, descriptor: &Font) -> Result; @@ -214,15 +222,21 @@ pub trait PlatformTextSystem: Send + Sync { ) -> Vec; } +/// Basic metadata about the current application and operating system. #[derive(Clone, Debug)] pub struct AppMetadata { + /// The name of the current operating system pub os_name: &'static str, + + /// The operating system's version pub os_version: Option, + + /// The current version of the application pub app_version: Option, } #[derive(PartialEq, Eq, Hash, Clone)] -pub enum AtlasKey { +pub(crate) enum AtlasKey { Glyph(RenderGlyphParams), Svg(RenderSvgParams), Image(RenderImageParams), @@ -262,7 +276,7 @@ impl From for AtlasKey { } } -pub trait PlatformAtlas: Send + Sync { +pub(crate) trait PlatformAtlas: Send + Sync { fn get_or_insert_with<'a>( &self, key: &AtlasKey, @@ -274,7 +288,7 @@ pub trait PlatformAtlas: Send + Sync { #[derive(Clone, Debug, PartialEq, Eq)] #[repr(C)] -pub struct AtlasTile { +pub(crate) struct AtlasTile { pub(crate) texture_id: AtlasTextureId, pub(crate) tile_id: TileId, pub(crate) bounds: Bounds, diff --git a/crates/gpui/src/platform/mac/display.rs b/crates/gpui/src/platform/mac/display.rs index 2b72c335c80e80f700d6caa25fd64d5c8bf4f414..95ec83cd5a9d120fbcbe531a5cec3c9dafa3c95a 100644 --- a/crates/gpui/src/platform/mac/display.rs +++ b/crates/gpui/src/platform/mac/display.rs @@ -11,7 +11,6 @@ use core_graphics::{ geometry::{CGPoint, CGRect, CGSize}, }; use objc::{msg_send, sel, sel_impl}; -use std::any::Any; use uuid::Uuid; #[derive(Debug)] @@ -154,10 +153,6 @@ impl PlatformDisplay for MacDisplay { ])) } - fn as_any(&self) -> &dyn Any { - self - } - fn bounds(&self) -> Bounds { unsafe { let native_bounds = CGDisplayBounds(self.0); diff --git a/crates/gpui/src/platform/test/display.rs b/crates/gpui/src/platform/test/display.rs index 68dbb0fdf3466b9f3a2800bf68768aea2f5dd1e2..838d600147b86e62d2f8197cf695590dbdefd750 100644 --- a/crates/gpui/src/platform/test/display.rs +++ b/crates/gpui/src/platform/test/display.rs @@ -31,10 +31,6 @@ impl PlatformDisplay for TestDisplay { Ok(self.uuid) } - fn as_any(&self) -> &dyn std::any::Any { - unimplemented!() - } - fn bounds(&self) -> crate::Bounds { self.bounds } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index de031704cd2888917534083bffd90e00c7e8b49b..b69c10c752296cb6ef765fc924ae030d46a450b4 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -93,7 +93,7 @@ impl Scene { } } - pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into) { + pub(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into) { let primitive = primitive.into(); let clipped_bounds = primitive .bounds() @@ -440,7 +440,7 @@ pub enum PrimitiveKind { Surface, } -pub enum Primitive { +pub(crate) enum Primitive { Shadow(Shadow), Quad(Quad), Path(Path), @@ -589,7 +589,7 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] -pub struct MonochromeSprite { +pub(crate) struct MonochromeSprite { pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, @@ -622,7 +622,7 @@ impl From for Primitive { #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] -pub struct PolychromeSprite { +pub(crate) struct PolychromeSprite { pub view_id: ViewId, pub layer_id: LayerId, pub order: DrawOrder, diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 34470aff021297d9b8a12025e5348550cd6111c5..27d216dd50e673659e9873908d7a8f54364a256f 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -47,7 +47,7 @@ pub struct TextSystem { } impl TextSystem { - pub fn new(platform_text_system: Arc) -> Self { + pub(crate) fn new(platform_text_system: Arc) -> Self { TextSystem { line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())), platform_text_system, diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index f6963dbfd4ed6dec3c467741a45d6c5531aa0788..1c5b2a8f993324eb031a3e6c04c227cf310c1011 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -13,7 +13,7 @@ pub struct LineWrapper { impl LineWrapper { pub const MAX_INDENT: u32 = 256; - pub fn new( + pub(crate) fn new( font_id: FontId, font_size: Pixels, text_system: Arc, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index bbe5e781094e33e8a53845d699e50ecd21552871..afc06ad193659e7c015f2039f2efaa0f95a3297a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1809,9 +1809,9 @@ mod tests { assert!(workspace.active_item(cx).is_none()); }) .unwrap(); - editor_1.assert_dropped(); - editor_2.assert_dropped(); - buffer.assert_dropped(); + editor_1.assert_released(); + editor_2.assert_released(); + buffer.assert_released(); } #[gpui::test] From 57400e9687da088d8a55515926c11a5328fd8571 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 14:31:21 -0800 Subject: [PATCH 280/334] Fix typos detected by crate-ci/typos --- assets/themes/src/vscode/dracula/dracula.json | 2 +- .../src/vscode/night-owl/night-owl-light.json | 6 +++--- .../themes/src/vscode/night-owl/night-owl.json | 6 +++--- assets/themes/src/vscode/noctis/azureus.json | 2 +- assets/themes/src/vscode/noctis/bordo.json | 2 +- assets/themes/src/vscode/noctis/hibernus.json | 2 +- assets/themes/src/vscode/noctis/lilac.json | 2 +- assets/themes/src/vscode/noctis/lux.json | 2 +- assets/themes/src/vscode/noctis/minimus.json | 2 +- assets/themes/src/vscode/noctis/noctis.json | 2 +- assets/themes/src/vscode/noctis/obscuro.json | 2 +- assets/themes/src/vscode/noctis/sereno.json | 2 +- assets/themes/src/vscode/noctis/uva.json | 2 +- assets/themes/src/vscode/noctis/viola.json | 2 +- .../palenight/palenight-mild-contrast.json | 10 +++++----- .../src/vscode/palenight/palenight-operator.json | 10 +++++----- .../themes/src/vscode/palenight/palenight.json | 10 +++++----- crates/ai/src/prompts/base.rs | 10 +++++----- crates/ai/src/prompts/repository_context.rs | 6 +++--- crates/ai/src/providers/open_ai/completion.rs | 2 +- crates/assistant/src/assistant_panel.rs | 2 +- crates/collab/src/db/queries/buffers.rs | 4 ++-- crates/collab/src/tests/channel_tests.rs | 2 +- crates/collab/src/tests/editor_tests.rs | 10 +++++----- .../collab_ui/src/collab_panel/channel_modal.rs | 4 ++-- crates/copilot_ui/src/sign_in.rs | 4 ++-- crates/editor/src/editor.rs | 4 ++-- crates/editor/src/inlay_hint_cache.rs | 12 ++++++------ crates/fs/src/repository.rs | 2 +- crates/gpui/src/executor.rs | 4 ++-- crates/gpui/src/keymap/matcher.rs | 4 ++-- crates/gpui/src/platform/keystroke.rs | 2 +- crates/gpui/src/platform/mac.rs | 2 +- ...window_appearence.rs => window_appearance.rs} | 0 crates/gpui/src/style.rs | 6 +++--- crates/gpui/src/window.rs | 2 +- crates/gpui_macros/src/gpui_macros.rs | 2 +- crates/language/src/buffer_tests.rs | 2 +- crates/lsp/src/lsp.rs | 2 +- crates/multi_buffer/src/multi_buffer.rs | 16 ++++++++-------- crates/project/src/lsp_command.rs | 2 +- crates/project/src/project.rs | 4 ++-- crates/project/src/project_settings.rs | 2 +- crates/project_panel/src/file_associations.rs | 2 +- crates/recent_projects/src/recent_projects.rs | 2 +- .../derive_refineable/src/derive_refineable.rs | 12 ++++++------ crates/search/src/project_search.rs | 2 +- crates/semantic_index/README.md | 2 +- crates/semantic_index/src/parsing.rs | 2 +- .../semantic_index/src/semantic_index_tests.rs | 8 ++++---- crates/sqlez/src/migrations.rs | 2 +- crates/storybook/src/stories/text.rs | 4 ++-- crates/terminal_view/src/terminal_view.rs | 4 ++-- crates/theme/src/styles/syntax.rs | 2 +- crates/theme_importer/src/main.rs | 4 ++-- crates/ui/src/components/button/button_like.rs | 2 +- crates/ui/src/components/popover.rs | 2 +- crates/ui/src/styles/elevation.rs | 2 +- crates/vcs_menu/src/lib.rs | 2 +- crates/vim/src/normal/repeat.rs | 2 +- crates/vim/src/normal/search.rs | 6 +++--- crates/vim/src/test/neovim_connection.rs | 2 +- crates/vim/src/visual.rs | 8 ++++---- crates/welcome/src/base_keymap_setting.rs | 2 +- crates/workspace/src/pane.rs | 2 +- crates/workspace/src/workspace.rs | 2 +- docs/old/building-zed.md | 2 +- docs/old/zed/syntax-highlighting.md | 2 +- docs/src/configuring_zed.md | 2 +- docs/src/developing_zed__adding_languages.md | 2 +- docs/src/developing_zed__building_zed.md | 2 +- 71 files changed, 133 insertions(+), 133 deletions(-) rename crates/gpui/src/platform/mac/{window_appearence.rs => window_appearance.rs} (100%) diff --git a/assets/themes/src/vscode/dracula/dracula.json b/assets/themes/src/vscode/dracula/dracula.json index 6604a094d5a194f74d378d25c38fb47a3f29c539..e9a29dec179baf2e518e6e82519adafe01ec879d 100644 --- a/assets/themes/src/vscode/dracula/dracula.json +++ b/assets/themes/src/vscode/dracula/dracula.json @@ -1024,7 +1024,7 @@ } }, { - "name": "SCSS attibute selector strings", + "name": "SCSS attribute selector strings", "scope": ["meta.attribute-selector.scss"], "settings": { "foreground": "#F1FA8C" diff --git a/assets/themes/src/vscode/night-owl/night-owl-light.json b/assets/themes/src/vscode/night-owl/night-owl-light.json index 81e0fc0092279aec3298ff2f77a81137e0340a68..627a55ac62b641b4b112fdf9388f6626fe64a018 100644 --- a/assets/themes/src/vscode/night-owl/night-owl-light.json +++ b/assets/themes/src/vscode/night-owl/night-owl-light.json @@ -892,14 +892,14 @@ } }, { - "name": "CoffeScript Variable Assignment", + "name": "CoffeeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#31e1eb" } }, { - "name": "CoffeScript Parameter Function", + "name": "CoffeeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#403f53" @@ -1708,7 +1708,7 @@ "keyword.operator.type", "keyword.operator", "keyword", - "punctuation.definintion.string", + "punctuation.definition.string", "punctuation", "variable.other.readwrite.js", "storage.type", diff --git a/assets/themes/src/vscode/night-owl/night-owl.json b/assets/themes/src/vscode/night-owl/night-owl.json index 6d41b6299b4b911ff5d9952e56255a090c981704..b16c22fb6afc415b17e6f78873b22ce2844eed5a 100644 --- a/assets/themes/src/vscode/night-owl/night-owl.json +++ b/assets/themes/src/vscode/night-owl/night-owl.json @@ -926,14 +926,14 @@ } }, { - "name": "CoffeScript Variable Assignment", + "name": "CoffeeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#31e1eb" } }, { - "name": "CoffeScript Parameter Function", + "name": "CoffeeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#d6deeb" @@ -1817,7 +1817,7 @@ "keyword.operator.type", "keyword.operator", "keyword", - "punctuation.definintion.string", + "punctuation.definition.string", "punctuation", "variable.other.readwrite.js", "storage.type", diff --git a/assets/themes/src/vscode/noctis/azureus.json b/assets/themes/src/vscode/noctis/azureus.json index d550e74811b63e329f46142f6c5bff6e51584b61..300113a59d0877077534d362c6f5359ccc3e8c48 100644 --- a/assets/themes/src/vscode/noctis/azureus.json +++ b/assets/themes/src/vscode/noctis/azureus.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/bordo.json b/assets/themes/src/vscode/noctis/bordo.json index a6c4853c3b078e7373f69ae1084ab7a9d5c47784..21c8a13511557dfa4cf09fd608af8b0f684ca7ae 100644 --- a/assets/themes/src/vscode/noctis/bordo.json +++ b/assets/themes/src/vscode/noctis/bordo.json @@ -389,7 +389,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/hibernus.json b/assets/themes/src/vscode/noctis/hibernus.json index a20a19289ea539b675cb42b3480eb6ff57e90e53..a2870e39058ad6ac9ecd722b28dde0823dafd926 100644 --- a/assets/themes/src/vscode/noctis/hibernus.json +++ b/assets/themes/src/vscode/noctis/hibernus.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/lilac.json b/assets/themes/src/vscode/noctis/lilac.json index 26e0fe422376496a3a91cc7d191a7057dc986073..a54b4e3c50de40e6a06b58213e9b19eaed00953f 100644 --- a/assets/themes/src/vscode/noctis/lilac.json +++ b/assets/themes/src/vscode/noctis/lilac.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/lux.json b/assets/themes/src/vscode/noctis/lux.json index 1f72b0e59cab91cb2255ee1438ace7b0102dfbcf..34dc89460e20e332a3e2eaee82ea2dec518a78c1 100644 --- a/assets/themes/src/vscode/noctis/lux.json +++ b/assets/themes/src/vscode/noctis/lux.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/minimus.json b/assets/themes/src/vscode/noctis/minimus.json index 88493d99d5993b6d72ef9a1a81228b8a82fe54c3..a347af76601a975f60ebf2f6c47a7f7d641f7885 100644 --- a/assets/themes/src/vscode/noctis/minimus.json +++ b/assets/themes/src/vscode/noctis/minimus.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/noctis.json b/assets/themes/src/vscode/noctis/noctis.json index cc270fe526f10f3f1fea30464390f1d8d5a76c8e..61e90c46a9a37752743052edb6b7decb44e3871a 100644 --- a/assets/themes/src/vscode/noctis/noctis.json +++ b/assets/themes/src/vscode/noctis/noctis.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/obscuro.json b/assets/themes/src/vscode/noctis/obscuro.json index 26d1a02de84a4bad2056444302b5e9d83faa8312..97e6f2d71a63b82bc688374f87e5333a5137d09c 100644 --- a/assets/themes/src/vscode/noctis/obscuro.json +++ b/assets/themes/src/vscode/noctis/obscuro.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/sereno.json b/assets/themes/src/vscode/noctis/sereno.json index 05768aff356e40078973f34650143e9089e1971d..b81da1edcecf18a3a4d52a58c18af2986c172485 100644 --- a/assets/themes/src/vscode/noctis/sereno.json +++ b/assets/themes/src/vscode/noctis/sereno.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/uva.json b/assets/themes/src/vscode/noctis/uva.json index 6ccbff372b8a965d9451279380f07f069b8f8f67..d4139faaf3a5c14356106d0b0da43501cfc554fd 100644 --- a/assets/themes/src/vscode/noctis/uva.json +++ b/assets/themes/src/vscode/noctis/uva.json @@ -389,7 +389,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/viola.json b/assets/themes/src/vscode/noctis/viola.json index 4d474ad31173c6e8e5888faa44e501cbc0e95aaa..889d2dfc2a96e7707b2a72150f262fa14a61f548 100644 --- a/assets/themes/src/vscode/noctis/viola.json +++ b/assets/themes/src/vscode/noctis/viola.json @@ -389,7 +389,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fileds", + "keyword.other.class.fields", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/palenight/palenight-mild-contrast.json b/assets/themes/src/vscode/palenight/palenight-mild-contrast.json index 7533d90ffd5752e5ea160ab2c686a2173aa9e4eb..598a186692c928e5c7cfaabc1f1c073aea74d303 100644 --- a/assets/themes/src/vscode/palenight/palenight-mild-contrast.json +++ b/assets/themes/src/vscode/palenight/palenight-mild-contrast.json @@ -797,14 +797,14 @@ } }, { - "name": "CoffeScript Variable Assignment", + "name": "CoffeeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#89DDFF" } }, { - "name": "CoffeScript Parameter Function", + "name": "CoffeeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#bfc7d5" @@ -1523,14 +1523,14 @@ } }, { - "name": "handlebars enitity attribute names", + "name": "handlebars entity attribute names", "scope": "entity.other.attribute-name.handlebars", "settings": { "foreground": "#89DDFF" } }, { - "name": "handlebars enitity attribute values", + "name": "handlebars entity attribute values", "scope": "entity.other.attribute-value.handlebars variable.parameter.handlebars", "settings": { "foreground": "#7986E7" @@ -1558,7 +1558,7 @@ "keyword.operator.expression.in", "keyword.operator.type", "punctuation.section.embedded.js", - "punctuation.definintion.string", + "punctuation.definition.string", "punctuation" ], "settings": { diff --git a/assets/themes/src/vscode/palenight/palenight-operator.json b/assets/themes/src/vscode/palenight/palenight-operator.json index 450d36cb9ae1233086847429ec795d5ff8e41a9f..635a2ff7607c455cef191222fc5f13fb8c63024b 100644 --- a/assets/themes/src/vscode/palenight/palenight-operator.json +++ b/assets/themes/src/vscode/palenight/palenight-operator.json @@ -797,14 +797,14 @@ } }, { - "name": "CoffeScript Variable Assignment", + "name": "CoffeeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#89DDFF" } }, { - "name": "CoffeScript Parameter Function", + "name": "CoffeeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#bfc7d5" @@ -1523,14 +1523,14 @@ } }, { - "name": "handlebars enitity attribute names", + "name": "handlebars entity attribute names", "scope": "entity.other.attribute-name.handlebars", "settings": { "foreground": "#89DDFF" } }, { - "name": "handlebars enitity attribute values", + "name": "handlebars entity attribute values", "scope": "entity.other.attribute-value.handlebars variable.parameter.handlebars", "settings": { "foreground": "#7986E7" @@ -1558,7 +1558,7 @@ "keyword.operator.expression.in", "keyword.operator.type", "punctuation.section.embedded.js", - "punctuation.definintion.string", + "punctuation.definition.string", "punctuation" ], "settings": { diff --git a/assets/themes/src/vscode/palenight/palenight.json b/assets/themes/src/vscode/palenight/palenight.json index cfbf2f8788c13cc66abfeccf9b0d619416fb642b..5cf68749f42ad9d0b81cfaf06cb8639cb8d3e717 100644 --- a/assets/themes/src/vscode/palenight/palenight.json +++ b/assets/themes/src/vscode/palenight/palenight.json @@ -797,14 +797,14 @@ } }, { - "name": "CoffeScript Variable Assignment", + "name": "CoffeeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#89DDFF" } }, { - "name": "CoffeScript Parameter Function", + "name": "CoffeeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#bfc7d5" @@ -1523,14 +1523,14 @@ } }, { - "name": "handlebars enitity attribute names", + "name": "handlebars entity attribute names", "scope": "entity.other.attribute-name.handlebars", "settings": { "foreground": "#89DDFF" } }, { - "name": "handlebars enitity attribute values", + "name": "handlebars entity attribute values", "scope": "entity.other.attribute-value.handlebars variable.parameter.handlebars", "settings": { "foreground": "#7986E7" @@ -1558,7 +1558,7 @@ "keyword.operator.expression.in", "keyword.operator.type", "punctuation.section.embedded.js", - "punctuation.definintion.string", + "punctuation.definition.string", "punctuation" ], "settings": { diff --git a/crates/ai/src/prompts/base.rs b/crates/ai/src/prompts/base.rs index 75bad00154b001a356f35cb5d80e4ac4962fe0f9..5e624f23acb8e416676251ed728711756ea1ae6d 100644 --- a/crates/ai/src/prompts/base.rs +++ b/crates/ai/src/prompts/base.rs @@ -81,8 +81,8 @@ impl PromptChain { pub fn generate(&self, truncate: bool) -> anyhow::Result<(String, usize)> { // Argsort based on Prompt Priority - let seperator = "\n"; - let seperator_tokens = self.args.model.count_tokens(seperator)?; + let separator = "\n"; + let separator_tokens = self.args.model.count_tokens(separator)?; let mut sorted_indices = (0..self.templates.len()).collect::>(); sorted_indices.sort_by_key(|&i| Reverse(&self.templates[i].0)); @@ -104,7 +104,7 @@ impl PromptChain { prompts[idx] = template_prompt; if let Some(remaining_tokens) = tokens_outstanding { - let new_tokens = prompt_token_count + seperator_tokens; + let new_tokens = prompt_token_count + separator_tokens; tokens_outstanding = if remaining_tokens > new_tokens { Some(remaining_tokens - new_tokens) } else { @@ -117,9 +117,9 @@ impl PromptChain { prompts.retain(|x| x != ""); - let full_prompt = prompts.join(seperator); + let full_prompt = prompts.join(separator); let total_token_count = self.args.model.count_tokens(&full_prompt)?; - anyhow::Ok((prompts.join(seperator), total_token_count)) + anyhow::Ok((prompts.join(separator), total_token_count)) } } diff --git a/crates/ai/src/prompts/repository_context.rs b/crates/ai/src/prompts/repository_context.rs index 0d831c2cb2ee67dc1144d6f4c74493b8f27d84e7..89869c53a002c7451da892035b008d936a7c7f6d 100644 --- a/crates/ai/src/prompts/repository_context.rs +++ b/crates/ai/src/prompts/repository_context.rs @@ -68,7 +68,7 @@ impl PromptTemplate for RepositoryContext { let mut prompt = String::new(); let mut remaining_tokens = max_token_length.clone(); - let seperator_token_length = args.model.count_tokens("\n")?; + let separator_token_length = args.model.count_tokens("\n")?; for snippet in &args.snippets { let mut snippet_prompt = template.to_string(); let content = snippet.to_string(); @@ -79,9 +79,9 @@ impl PromptTemplate for RepositoryContext { if let Some(tokens_left) = remaining_tokens { if tokens_left >= token_count { writeln!(prompt, "{snippet_prompt}").unwrap(); - remaining_tokens = if tokens_left >= (token_count + seperator_token_length) + remaining_tokens = if tokens_left >= (token_count + separator_token_length) { - Some(tokens_left - token_count - seperator_token_length) + Some(tokens_left - token_count - separator_token_length) } else { Some(0) }; diff --git a/crates/ai/src/providers/open_ai/completion.rs b/crates/ai/src/providers/open_ai/completion.rs index c9a2abd0c8c3bc170ff39f16294f2fb976f00465..f99b7f95e346d275fade7628c0ea27375f84e7c9 100644 --- a/crates/ai/src/providers/open_ai/completion.rs +++ b/crates/ai/src/providers/open_ai/completion.rs @@ -273,7 +273,7 @@ impl CompletionProvider for OpenAICompletionProvider { ) -> BoxFuture<'static, Result>>> { // Currently the CompletionRequest for OpenAI, includes a 'model' parameter // This means that the model is determined by the CompletionRequest and not the CompletionProvider, - // which is currently model based, due to the langauge model. + // which is currently model based, due to the language model. // At some point in the future we should rectify this. let credential = self.credential.read().clone(); let request = stream_completion(credential, self.executor.clone(), prompt); diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index df3dc3754f66aff8d83a6fcd3b92edd38c7c4e45..ff12918e773efb3719402bc6212b7d69b51e73c6 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2917,7 +2917,7 @@ impl InlineAssistant { let semantic_permissioned = self.semantic_permissioned(cx); if let Some(semantic_index) = SemanticIndex::global(cx) { cx.spawn(|_, mut cx| async move { - // This has to be updated to accomodate for semantic_permissions + // This has to be updated to accommodate for semantic_permissions if semantic_permissioned.await.unwrap_or(false) { semantic_index .update(&mut cx, |index, cx| index.index_project(project, cx))? diff --git a/crates/collab/src/db/queries/buffers.rs b/crates/collab/src/db/queries/buffers.rs index 9eddb1f6187a80f4f88f8d13e9aff3f4c310941f..dc757e4d1a7cd48da4630dce15bcbc32c5186b6a 100644 --- a/crates/collab/src/db/queries/buffers.rs +++ b/crates/collab/src/db/queries/buffers.rs @@ -149,7 +149,7 @@ impl Database { .await?; // If the buffer epoch hasn't changed since the client lost - // connection, then the client's buffer can be syncronized with + // connection, then the client's buffer can be synchronized with // the server's buffer. if buffer.epoch as u64 != client_buffer.epoch { log::info!("can't rejoin buffer, epoch has changed"); @@ -962,7 +962,7 @@ fn version_from_storage(version: &Vec) -> Vec Option { match operation.variant? { proto::operation::Variant::Edit(edit) => Some(text::Operation::Edit(EditOperation { diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index e80fe0fdca312bed54d98fce0a1ea69a8a4a6e86..7fbdf8ba7fc09a404f9824205c1d06d94c58f190 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -203,7 +203,7 @@ async fn test_core_channels( executor.run_until_parked(); // Observe that client B is now an admin of channel A, and that - // their admin priveleges extend to subchannels of channel A. + // their admin privileges extend to subchannels of channel A. assert_channel_invitations(client_b.channel_store(), cx_b, &[]); assert_channels( client_b.channel_store(), diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 0c3601b07531bf5c77459fd5530a31ba8ef68717..539e61ec964b545507e40d29abaf0fef88c862de 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -1201,7 +1201,7 @@ async fn test_on_input_format_from_host_to_guest( executor.run_until_parked(); // Receive an OnTypeFormatting request as the host's language server. - // Return some formattings from the host's language server. + // Return some formatting from the host's language server. fake_language_server.handle_request::( |params, _| async move { assert_eq!( @@ -1220,7 +1220,7 @@ async fn test_on_input_format_from_host_to_guest( }, ); - // Open the buffer on the guest and see that the formattings worked + // Open the buffer on the guest and see that the formatting worked let buffer_b = project_b .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await @@ -1339,7 +1339,7 @@ async fn test_on_input_format_from_guest_to_host( }); // Receive an OnTypeFormatting request as the host's language server. - // Return some formattings from the host's language server. + // Return some formatting from the host's language server. executor.start_waiting(); fake_language_server .handle_request::(|params, _| async move { @@ -1362,7 +1362,7 @@ async fn test_on_input_format_from_guest_to_host( .unwrap(); executor.finish_waiting(); - // Open the buffer on the host and see that the formattings worked + // Open the buffer on the host and see that the formatting worked let buffer_a = project_a .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await @@ -1836,7 +1836,7 @@ async fn test_inlay_hint_refresh_is_forwarded( assert_eq!( inlay_cache.version(), 1, - "Should update cache verison after first hints" + "Should update cache version after first hints" ); }); diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 11890bcbe6d3dace458827583c13d331c6670e7b..e92422d76d79e7b2706aed83b892031f9b4b3a96 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -111,7 +111,7 @@ impl ChannelModal { .detach(); } - fn set_channel_visiblity(&mut self, selection: &Selection, cx: &mut ViewContext) { + fn set_channel_visibility(&mut self, selection: &Selection, cx: &mut ViewContext) { self.channel_store.update(cx, |channel_store, cx| { channel_store .set_channel_visibility( @@ -189,7 +189,7 @@ impl Render for ChannelModal { ui::Selection::Unselected }, ) - .on_click(cx.listener(Self::set_channel_visiblity)), + .on_click(cx.listener(Self::set_channel_visibility)), ) .child(Label::new("Public").size(LabelSize::Small)), ) diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index f78a82699dc3c70925accc3e70a3242e9aad5061..2bea2e016ce9eda8215fec825e3d10312af011a3 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -96,7 +96,7 @@ impl CopilotCodeVerification { .items_center() .child(Headline::new("Use Github Copilot in Zed.").size(HeadlineSize::Large)) .child( - Label::new("Using Copilot requres an active subscription on Github.") + Label::new("Using Copilot requires an active subscription on Github.") .color(Color::Muted), ) .child(Self::render_device_code(data, cx)) @@ -139,7 +139,7 @@ impl CopilotCodeVerification { "You can enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.", ).color(Color::Warning)) .child( - Button::new("copilot-subscribe-button", "Subscibe on Github") + Button::new("copilot-subscribe-button", "Subscribe on Github") .full_width() .on_click(|_, cx| cx.open_url(COPILOT_SIGN_UP_URL)), ) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 30b0a73d37e093bcb456d4fa6cf8e0c2ff98d5ff..ca1f22d158fc87b1af4d92443898652e4d3cbb87 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8736,7 +8736,7 @@ impl Editor { ) { match event { multi_buffer::Event::Edited { - sigleton_buffer_edited, + singleton_buffer_edited, } => { self.refresh_active_diagnostics(cx); self.refresh_code_actions(cx); @@ -8746,7 +8746,7 @@ impl Editor { cx.emit(EditorEvent::BufferEdited); cx.emit(SearchEvent::MatchesInvalidated); - if *sigleton_buffer_edited { + if *singleton_buffer_edited { if let Some(project) = &self.project { let project = project.read(cx); let languages_affected = multibuffer diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 59c6b8605c1001440999e3f6909db300cf33e392..8fdc9f1a7de27ae67199d9cc11b937b1881bfa27 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -925,14 +925,14 @@ async fn fetch_and_update_hints( log::trace!("Fetched hints: {new_hints:?}"); let background_task_buffer_snapshot = buffer_snapshot.clone(); - let backround_fetch_range = fetch_range.clone(); + let background_fetch_range = fetch_range.clone(); let new_update = cx .background_executor() .spawn(async move { calculate_hint_updates( query.excerpt_id, invalidate, - backround_fetch_range, + background_fetch_range, new_hints, &background_task_buffer_snapshot, cached_excerpt_hints, @@ -1449,7 +1449,7 @@ pub mod tests { assert_eq!( editor.inlay_hint_cache().version, edits_made, - "Cache version should udpate once after the work task is done" + "Cache version should update once after the work task is done" ); }); } @@ -1599,7 +1599,7 @@ pub mod tests { assert_eq!( expected_hints, cached_hint_labels(editor), - "Markdown editor should have a separate verison, repeating Rust editor rules" + "Markdown editor should have a separate version, repeating Rust editor rules" ); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(editor.inlay_hint_cache().version, 1); @@ -2612,7 +2612,7 @@ pub mod tests { "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints" ); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); - assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison"); + assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the version"); }); _ = editor.update(cx, |editor, cx| { @@ -2728,7 +2728,7 @@ pub mod tests { expected_hints, cached_hint_labels(editor), "After multibuffer edit, editor gets scolled back to the last selection; \ - all hints should be invalidated and requeried for all of its visible excerpts" + all hints should be invalidated and required for all of its visible excerpts" ); assert_eq!(expected_hints, visible_hint_labels(editor, cx)); diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index cf5c65105c9e9c473967f8adb9f7de04b6d8f567..ecb2a93577f20de437ea3762aa1d4a740848e294 100644 --- a/crates/fs/src/repository.rs +++ b/crates/fs/src/repository.rs @@ -29,7 +29,7 @@ pub trait GitRepository: Send { fn branch_name(&self) -> Option; /// Get the statuses of all of the files in the index that start with the given - /// path and have changes with resepect to the HEAD commit. This is fast because + /// path and have changes with respect to the HEAD commit. This is fast because /// the index stores hashes of trees, so that unchanged directories can be skipped. fn staged_statuses(&self, path_prefix: &Path) -> TreeMap; diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index fc60cb1ec6afcd1c79f5b561a436eac4635c47bc..585cbc80d197e99752cc6ea24ff71d8ed778097f 100644 --- a/crates/gpui/src/executor.rs +++ b/crates/gpui/src/executor.rs @@ -149,7 +149,7 @@ impl BackgroundExecutor { Task::Spawned(task) } - /// Used by the test harness to run an async test in a syncronous fashion. + /// Used by the test harness to run an async test in a synchronous fashion. #[cfg(any(test, feature = "test-support"))] #[track_caller] pub fn block_test(&self, future: impl Future) -> R { @@ -276,7 +276,7 @@ impl BackgroundExecutor { /// Returns a task that will complete after the given duration. /// Depending on other concurrent tasks the elapsed duration may be longer - /// than reqested. + /// than requested. pub fn timer(&self, duration: Duration) -> Task<()> { let (runnable, task) = async_task::spawn(async move {}, { let dispatcher = self.dispatcher.clone(); diff --git a/crates/gpui/src/keymap/matcher.rs b/crates/gpui/src/keymap/matcher.rs index 5410ddce06e9ca999aaee0911fd7a69d6a0e61c8..36c8035c8f27f10fccfe06b803a66a4fe9ed452c 100644 --- a/crates/gpui/src/keymap/matcher.rs +++ b/crates/gpui/src/keymap/matcher.rs @@ -445,7 +445,7 @@ mod tests { KeyMatch::Some(vec![Box::new(Dollar)]) ); - // handle Brazillian quote (quote key then space key) + // handle Brazilian quote (quote key then space key) assert_eq!( matcher.match_keystroke( &Keystroke::parse("space->\"").unwrap(), @@ -454,7 +454,7 @@ mod tests { KeyMatch::Some(vec![Box::new(Quote)]) ); - // handle ctrl+` on a brazillian keyboard + // handle ctrl+` on a brazilian keyboard assert_eq!( matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]), KeyMatch::Some(vec![Box::new(Backtick)]) diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index cadb9c92e3f4dc53d23671dfb630a8560bf55c7e..64a901789abb688c36c8dd2f2eef9c5fb16a34e8 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -19,7 +19,7 @@ impl Keystroke { // the ime_key or the key. On some non-US keyboards keys we use in our // bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard), // and on some keyboards the IME handler converts a sequence of keys into a - // specific character (for example `"` is typed as `" space` on a brazillian keyboard). + // specific character (for example `"` is typed as `" space` on a brazilian keyboard). pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> { let mut possibilities = SmallVec::new(); match self.ime_key.as_ref() { diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index 8f48b8ea94d8aa2193545267dd3d85a021a9f96c..3cc74a968399dcc0fffcf8c795137262e66df8de 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -10,7 +10,7 @@ mod open_type; mod platform; mod text_system; mod window; -mod window_appearence; +mod window_appearance; use crate::{px, size, GlobalPixels, Pixels, Size}; use cocoa::{ diff --git a/crates/gpui/src/platform/mac/window_appearence.rs b/crates/gpui/src/platform/mac/window_appearance.rs similarity index 100% rename from crates/gpui/src/platform/mac/window_appearence.rs rename to crates/gpui/src/platform/mac/window_appearance.rs diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 8fdb926b27ebc8e1ab6b4531a5902e0395f53f8b..095233280edefc0b11d85e3a4ee255f54c8da13d 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -42,7 +42,7 @@ pub struct Style { #[refineable] pub inset: Edges, - // Size properies + // Size properties /// Sets the initial size of the item #[refineable] pub size: Size, @@ -79,7 +79,7 @@ pub struct Style { #[refineable] pub gap: Size, - // Flexbox properies + // Flexbox properties /// Which direction does the main axis flow in? pub flex_direction: FlexDirection, /// Should elements wrap, or stay in a single line? @@ -502,7 +502,7 @@ impl Default for Style { max_size: Size::auto(), aspect_ratio: None, gap: Size::default(), - // Aligment + // Alignment align_items: None, align_self: None, align_content: None, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 0269ccfb6c8ef55df1696157302f6156e041f744..0e4bae3406416a75d8c24b07be20be7cbfa0a84b 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2130,7 +2130,7 @@ impl<'a> WindowContext<'a> { .unwrap(); // Actual: Option <- View - // Requested: () <- AnyElemet + // Requested: () <- AnyElement let state = state_box .take() .expect("element state is already on the stack"); diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index 1187d96ca320abbc51b66b24b0639b1211b451dc..aef1785bb5a56c6acbc9d42d76286a0ed8a13133 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -53,7 +53,7 @@ pub fn style_helpers(input: TokenStream) -> TokenStream { /// variety of scenarios and interleavings just by changing the seed. /// /// #[gpui::test] also takes three different arguments: -/// - `#[gpui::test(interations=10)]` will run the test ten times with a different initial SEED. +/// - `#[gpui::test(iterations=10)]` will run the test ten times with a different initial SEED. /// - `#[gpui::test(retries=3)]` will run the test up to four times if it fails to try and make it pass. /// - `#[gpui::test(on_failure="crate::test::report_failure")]` will call the specified function after the /// tests fail so that you can write out more detail about the failure. diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 780483c5ca24fbb5ec43f9652b54f6c9ba5b0c30..6ad345d4e324d96dbe13333d0729db3cc6406671 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -275,7 +275,7 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) { let version_before_format = format_diff.base_version.clone(); buffer.apply_diff(format_diff, cx); - // The outcome depends on the order of concurrent taks. + // The outcome depends on the order of concurrent tasks. // // If the edit occurred while searching for trailing whitespace ranges, // then the trailing whitespace region touched by the edit is left intact. diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 788c424373deca7c1490dd954fa005e0943d8a99..30cc0c07d96ecd218732ef4a24c5b2a60a5642a8 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -839,7 +839,7 @@ impl LanguageServer { futures::select! { response = rx.fuse() => { let elapsed = started.elapsed(); - log::trace!("Took {elapsed:?} to recieve response to {method:?} id {id}"); + log::trace!("Took {elapsed:?} to receive response to {method:?} id {id}"); response? } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index f3ecd2d25f2b3866b1f6c3f0ce68415fa7cd53ae..9f78480136e2e6eff71fb3016a2c9c99d4297b6f 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -72,7 +72,7 @@ pub enum Event { ids: Vec, }, Edited { - sigleton_buffer_edited: bool, + singleton_buffer_edited: bool, }, TransactionUndone { transaction_id: TransactionId, @@ -1112,7 +1112,7 @@ impl MultiBuffer { new: edit_start..edit_end, }]); cx.emit(Event::Edited { - sigleton_buffer_edited: false, + singleton_buffer_edited: false, }); cx.emit(Event::ExcerptsAdded { buffer, @@ -1138,7 +1138,7 @@ impl MultiBuffer { new: 0..0, }]); cx.emit(Event::Edited { - sigleton_buffer_edited: false, + singleton_buffer_edited: false, }); cx.emit(Event::ExcerptsRemoved { ids }); cx.notify(); @@ -1348,7 +1348,7 @@ impl MultiBuffer { self.subscriptions.publish_mut(edits); cx.emit(Event::Edited { - sigleton_buffer_edited: false, + singleton_buffer_edited: false, }); cx.emit(Event::ExcerptsRemoved { ids }); cx.notify(); @@ -1411,7 +1411,7 @@ impl MultiBuffer { ) { cx.emit(match event { language::Event::Edited => Event::Edited { - sigleton_buffer_edited: true, + singleton_buffer_edited: true, }, language::Event::DirtyChanged => Event::DirtyChanged, language::Event::Saved => Event::Saved, @@ -4280,13 +4280,13 @@ mod tests { events.read().as_slice(), &[ Event::Edited { - sigleton_buffer_edited: false + singleton_buffer_edited: false }, Event::Edited { - sigleton_buffer_edited: false + singleton_buffer_edited: false }, Event::Edited { - sigleton_buffer_edited: false + singleton_buffer_edited: false } ] ); diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 52836f4c0030e005eb19d396c77bfb49fc0ea604..2c2bed87173e2d3e7fff124815e4f89f852a103b 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -2253,7 +2253,7 @@ impl LspCommand for InlayHints { language_server_for_buffer(&project, &buffer, server_id, &mut cx)?; // `typescript-language-server` adds padding to the left for type hints, turning // `const foo: boolean` into `const foo : boolean` which looks odd. - // `rust-analyzer` does not have the padding for this case, and we have to accomodate both. + // `rust-analyzer` does not have the padding for this case, and we have to accommodate both. // // We could trim the whole string, but being pessimistic on par with the situation above, // there might be a hint with multiple whitespaces at the end(s) which we need to display properly. diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 5f37bbfce6483e359866a0dadb1d63b2e32b4651..c5dc88d4479ef1627a7da56344c16695077756b3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5578,7 +5578,7 @@ impl Project { // 3. We run a scan over all the candidate buffers on multiple background threads. // We cannot assume that there will even be a match - while at least one match // is guaranteed for files obtained from FS, the buffers we got from memory (unsaved files/unnamed buffers) might not have a match at all. - // There is also an auxilliary background thread responsible for result gathering. + // There is also an auxiliary background thread responsible for result gathering. // This is where the sorted list of buffers comes into play to maintain sorted order; Whenever this background thread receives a notification (buffer has/doesn't have matches), // it keeps it around. It reports matches in sorted order, though it accepts them in unsorted order as well. // As soon as the match info on next position in sorted order becomes available, it reports it (if it's a match) or skips to the next @@ -8550,7 +8550,7 @@ fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str { break; } else { if i > 0 { - // Acount for separator prior to this part + // Account for separator prior to this part literal_end += path::MAIN_SEPARATOR.len_utf8(); } literal_end += part.len(); diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 925109ac964044a2e93a4a1a7ba23b493a2434ed..9ec07bc088c49ef541b2af1b0c7ee1164f9a9eec 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -9,7 +9,7 @@ use std::sync::Arc; pub struct ProjectSettings { /// Configuration for language servers. /// - /// The following settings can be overriden for specific language servers: + /// The following settings can be overridden for specific language servers: /// - initialization_options /// To override settings for a language, add an entry for that language server's /// name to the lsp value. diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index 82aebe7913133d2aa7f673f21387f2c7a8ad3ca3..0ddcfc9285eb31b7f5be50d57d6ef8dd14cebb42 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -44,7 +44,7 @@ impl FileAssociations { pub fn get_icon(path: &Path, cx: &AppContext) -> Option> { let this = cx.has_global::().then(|| cx.global::())?; - // FIXME: Associate a type with the languages and have the file's langauge + // FIXME: Associate a type with the languages and have the file's language // override these associations maybe!({ let suffix = path.icon_suffix()?; diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 6208635e22d969bfa9219d7eb4d1466a35a0999d..1d8ddefcbcc674ff22f85aeda5c9f3fc67dc4a85 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -32,7 +32,7 @@ impl RecentProjects { fn new(delegate: RecentProjectsDelegate, rem_width: f32, cx: &mut ViewContext) -> Self { let picker = cx.new_view(|cx| Picker::new(delegate, cx)); let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent)); - // We do not want to block the UI on a potentially lenghty call to DB, so we're gonna swap + // We do not want to block the UI on a potentially lengthy call to DB, so we're gonna swap // out workspace locations once the future runs to completion. cx.spawn(|this, mut cx| async move { let workspaces = WORKSPACE_DB diff --git a/crates/refineable/derive_refineable/src/derive_refineable.rs b/crates/refineable/derive_refineable/src/derive_refineable.rs index 99418206462a0dc7bc3babd2f9bda534a69a0f39..bc906daece556106978f9788ff1502f004812180 100644 --- a/crates/refineable/derive_refineable/src/derive_refineable.rs +++ b/crates/refineable/derive_refineable/src/derive_refineable.rs @@ -141,7 +141,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { }) .collect(); - let refinement_refine_assigments: Vec = fields + let refinement_refine_assignments: Vec = fields .iter() .map(|field| { let name = &field.ident; @@ -161,7 +161,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { }) .collect(); - let refinement_refined_assigments: Vec = fields + let refinement_refined_assignments: Vec = fields .iter() .map(|field| { let name = &field.ident; @@ -181,7 +181,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { }) .collect(); - let from_refinement_assigments: Vec = fields + let from_refinement_assignments: Vec = fields .iter() .map(|field| { let name = &field.ident; @@ -272,11 +272,11 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { type Refinement = #refinement_ident #ty_generics; fn refine(&mut self, refinement: &Self::Refinement) { - #( #refinement_refine_assigments )* + #( #refinement_refine_assignments )* } fn refined(mut self, refinement: Self::Refinement) -> Self { - #( #refinement_refined_assigments )* + #( #refinement_refined_assignments )* self } } @@ -286,7 +286,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { { fn from(value: #refinement_ident #ty_generics) -> Self { Self { - #( #from_refinement_assigments )* + #( #from_refinement_assignments )* } } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 49eb24ce9ee9e267dc921b6ddb8eb10e92c83c97..55fe39310cd8bc2084a2644f8eaab49a83e891cb 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -2579,7 +2579,7 @@ pub mod tests { ); assert!( search_view_2.query_editor.focus_handle(cx).is_focused(cx), - "Focus should be moved into query editor fo the new window" + "Focus should be moved into query editor of the new window" ); }); }).unwrap(); diff --git a/crates/semantic_index/README.md b/crates/semantic_index/README.md index 85f83af121ed96a51ac84165c19cda3cd8aff7d4..75ccb41b84468bef319b6bc1957fd236c125ba95 100644 --- a/crates/semantic_index/README.md +++ b/crates/semantic_index/README.md @@ -10,7 +10,7 @@ nDCG@k: - "The relevance of result is represented by a score (also known as a 'grade') that is assigned to the search query. The scores of these results are then discounted based on their position in the search results -- did they get recommended first or last?" MRR@k: -- "Mean reciprocal rank quantifies the rank of the first relevant item found in teh recommendation list." +- "Mean reciprocal rank quantifies the rank of the first relevant item found in the recommendation list." MAP@k: - "Mean average precision averages the precision@k metric at each relevant item position in the recommendation list. diff --git a/crates/semantic_index/src/parsing.rs b/crates/semantic_index/src/parsing.rs index 427ac158c1b9e84ed4c64ca6700e08cc31269b7d..9f2db711ae0e97d5f7f21af7afd12f09de147a18 100644 --- a/crates/semantic_index/src/parsing.rs +++ b/crates/semantic_index/src/parsing.rs @@ -76,7 +76,7 @@ pub struct CodeContextRetriever { // Every match has an item, this represents the fundamental treesitter symbol and anchors the search // Every match has one or more 'name' captures. These indicate the display range of the item for deduplication. -// If there are preceeding comments, we track this with a context capture +// If there are preceding comments, we track this with a context capture // If there is a piece that should be collapsed in hierarchical queries, we capture it with a collapse capture // If there is a piece that should be kept inside a collapsed node, we capture it with a keep capture #[derive(Debug, Clone)] diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index e340b44a58377b8a9bda52786dea660637ce54c1..86eb6b84041fced41d1a0c433ebf62d10634c58f 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -110,7 +110,7 @@ async fn test_semantic_index(cx: &mut TestAppContext) { cx, ); - // Test Include Files Functonality + // Test Include Files Functionality let include_files = vec![PathMatcher::new("*.rs").unwrap()]; let exclude_files = vec![PathMatcher::new("*.rs").unwrap()]; let rust_only_search_results = semantic_index @@ -576,7 +576,7 @@ async fn test_code_context_retrieval_lua() { setmetatable(classdef, { __index = baseclass }) -- All class instances have a reference to the class object. classdef.class = classdef - --- Recursivly allocates the inheritance tree of the instance. + --- Recursively allocates the inheritance tree of the instance. -- @param mastertable The 'root' of the inheritance tree. -- @return Returns the instance with the allocated inheritance tree. function classdef.alloc(mastertable) @@ -607,7 +607,7 @@ async fn test_code_context_retrieval_lua() { setmetatable(classdef, { __index = baseclass }) -- All class instances have a reference to the class object. classdef.class = classdef - --- Recursivly allocates the inheritance tree of the instance. + --- Recursively allocates the inheritance tree of the instance. -- @param mastertable The 'root' of the inheritance tree. -- @return Returns the instance with the allocated inheritance tree. function classdef.alloc(mastertable) @@ -617,7 +617,7 @@ async fn test_code_context_retrieval_lua() { end"#.unindent(), 114), (r#" - --- Recursivly allocates the inheritance tree of the instance. + --- Recursively allocates the inheritance tree of the instance. -- @param mastertable The 'root' of the inheritance tree. -- @return Returns the instance with the allocated inheritance tree. function classdef.alloc(mastertable) diff --git a/crates/sqlez/src/migrations.rs b/crates/sqlez/src/migrations.rs index c0d125d4df9f654364088f6af31c40c9f3192950..f59b9dd40eddbce68412484fded2a729af0fefee 100644 --- a/crates/sqlez/src/migrations.rs +++ b/crates/sqlez/src/migrations.rs @@ -191,7 +191,7 @@ mod test { fn migrations_dont_rerun() { let connection = Connection::open_memory(Some("migrations_dont_rerun")); - // Create migration which clears a tabl + // Create migration which clears a table // Manually create the table for that migration with a row connection diff --git a/crates/storybook/src/stories/text.rs b/crates/storybook/src/stories/text.rs index 065b5bf795ba89fa1747c40fc1387ff00d4cb9c0..b7445ef95aac857a258c903a64ccde0c25497461 100644 --- a/crates/storybook/src/stories/text.rs +++ b/crates/storybook/src/stories/text.rs @@ -65,7 +65,7 @@ impl Render for TextStory { )) ) .usage(indoc! {r##" - // NOTE: When rendering text in a horizonal flex container, + // NOTE: When rendering text in a horizontal flex container, // Taffy will not pass width constraints down from the parent. // To fix this, render text in a parent with overflow: hidden @@ -149,7 +149,7 @@ impl Render for TextStory { // "Meanwhile, the lazy dog decided it was time for a change. ", // "He started daily workout routines, ate healthier and became the fastest dog in town.", // )))) -// // NOTE: When rendering text in a horizonal flex container, +// // NOTE: When rendering text in a horizontal flex container, // // Taffy will not pass width constraints down from the parent. // // To fix this, render text in a parent with overflow: hidden // .child(div().h_5()) diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index db4b21627f13a3e050888ca1cf18a06488484b1a..2f5e3ccf78bb9dfd557ee74f06636d666970850b 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -674,9 +674,9 @@ impl Render for TerminalView { self.can_navigate_to_selected_word, )), ) - .children(self.context_menu.as_ref().map(|(menu, positon, _)| { + .children(self.context_menu.as_ref().map(|(menu, position, _)| { overlay() - .position(*positon) + .position(*position) .anchor(gpui::AnchorCorner::TopLeft) .child(menu.clone()) })) diff --git a/crates/theme/src/styles/syntax.rs b/crates/theme/src/styles/syntax.rs index 0f35bf60a73aa634bf0953e19f4beea1354131c3..d6189f73e3e10da39918932559503787a129af64 100644 --- a/crates/theme/src/styles/syntax.rs +++ b/crates/theme/src/styles/syntax.rs @@ -127,7 +127,7 @@ impl SyntaxTheme { } } - // TOOD: Get this working with `#[cfg(test)]`. Why isn't it? + // TODO: Get this working with `#[cfg(test)]`. Why isn't it? pub fn new_test(colors: impl IntoIterator) -> Self { SyntaxTheme { highlights: colors diff --git a/crates/theme_importer/src/main.rs b/crates/theme_importer/src/main.rs index ff20d36a5df6ad3ffebbe0dc4f58a85ec1383707..0861b7efd8433729707a3c7d4d6fe8541366226a 100644 --- a/crates/theme_importer/src/main.rs +++ b/crates/theme_importer/src/main.rs @@ -188,7 +188,7 @@ fn main() -> Result<()> { let zed1_themes_path = PathBuf::from_str("assets/themes")?; - let zed1_theme_familes = [ + let zed1_theme_families = [ "Andromeda", "Atelier", "Ayu", @@ -207,7 +207,7 @@ fn main() -> Result<()> { ); let mut zed1_themes_by_family: IndexMap> = IndexMap::from_iter( - zed1_theme_familes + zed1_theme_families .into_iter() .map(|family| (family.to_string(), Vec::new())), ); diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 018d31dafdb4491bc88733117c8e42f66e789aa3..c2910acfc04bee67630c3d55b2ce2394559379f6 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -111,7 +111,7 @@ pub enum ButtonStyle { #[default] Subtle, - /// Used for buttons that only change forground color on hover and active states. + /// Used for buttons that only change foreground color on hover and active states. /// /// TODO: Better docs for this. Transparent, diff --git a/crates/ui/src/components/popover.rs b/crates/ui/src/components/popover.rs index 2e0c5bfec87b84f820bf3bdb512053460e90360f..ad72a1d9b6efd45cf16270ac0cb552b46732957a 100644 --- a/crates/ui/src/components/popover.rs +++ b/crates/ui/src/components/popover.rs @@ -12,7 +12,7 @@ use smallvec::SmallVec; /// user's mouse.) /// /// Example: A "new" menu with options like "new file", "new folder", etc, -/// Linear's "Display" menu, a profile menu that appers when you click your avatar. +/// Linear's "Display" menu, a profile menu that appears when you click your avatar. /// /// Related elements: /// diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index ec1848ca6093606aea9abcc926f242b20e0e727c..0aa3786a279242c8a3a301b497a60ab0c2c537ec 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -85,7 +85,7 @@ impl LayerIndex { } } -/// An appropriate z-index for the given layer based on its intended useage. +/// An appropriate z-index for the given layer based on its intended usage. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementIndex { Effect, diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index 67a12a852bdaeeae08f5fbcdc41ab11a91572605..44564ce878eae530cf23f48ea02ad44d93cd4892 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -342,7 +342,7 @@ impl PickerDelegate for BranchListDelegate { } let status = repo.change_branch(¤t_pick); if status.is_err() { - this.delegate.display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx); + this.delegate.display_error_toast(format!("Failed to check branch '{current_pick}', check for conflicts or unstashed files"), cx); status?; } this.cancel(&Default::default(), cx); diff --git a/crates/vim/src/normal/repeat.rs b/crates/vim/src/normal/repeat.rs index a643c126ef4edbd00442578059b9db55cb3e5774..e1570f9d6415a8878352c0b2c9c314313d8f6c51 100644 --- a/crates/vim/src/normal/repeat.rs +++ b/crates/vim/src/normal/repeat.rs @@ -152,7 +152,7 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) { let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1); - // if we came from insert mode we're just doing repititions 2 onwards. + // if we came from insert mode we're just doing repetitions 2 onwards. if from_insert_mode { count -= 1; new_actions[0] = actions[0].clone(); diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 7b5f4d3e59a3362c807922ae12ffaba98fdfd8eb..f85e3d9ba92415040114fbcfd61c55c5066dbf79 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -278,7 +278,7 @@ fn parse_replace_all(query: &str) -> Replacement { return Replacement::default(); } - let Some(delimeter) = chars.next() else { + let Some(delimiter) = chars.next() else { return Replacement::default(); }; @@ -301,13 +301,13 @@ fn parse_replace_all(query: &str) -> Replacement { buffer.push('$') // unescape escaped parens } else if phase == 0 && c == '(' || c == ')' { - } else if c != delimeter { + } else if c != delimiter { buffer.push('\\') } buffer.push(c) } else if c == '\\' { escaped = true; - } else if c == delimeter { + } else if c == delimiter { if phase == 0 { buffer = &mut replacement; phase = 1; diff --git a/crates/vim/src/test/neovim_connection.rs b/crates/vim/src/test/neovim_connection.rs index 363f6d43e38ab2a99cab89173e62ddc81bfe02f2..a2daf7499d887eee0d7cd6de363f305f7740387c 100644 --- a/crates/vim/src/test/neovim_connection.rs +++ b/crates/vim/src/test/neovim_connection.rs @@ -359,7 +359,7 @@ impl NeovimConnection { // to add one to the end in visual mode. match mode { Some(Mode::VisualBlock) if selection_row != cursor_row => { - // in zed we fake a block selecrtion by using multiple cursors (one per line) + // in zed we fake a block selection by using multiple cursors (one per line) // this code emulates that. // to deal with casees where the selection is not perfectly rectangular we extract // the content of the selection via the "a register to get the shape correctly. diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 1fd11167c6843206ddcef2b4ccee96a0f9886dae..0f29101d974f26e9c714ac47c1ade3331950b444 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -201,14 +201,14 @@ pub fn visual_block_motion( let mut row = tail.row(); loop { - let layed_out_line = map.layout_row(row, &text_layout_details); + let laid_out_line = map.layout_row(row, &text_layout_details); let start = DisplayPoint::new( row, - layed_out_line.closest_index_for_x(positions.start) as u32, + laid_out_line.closest_index_for_x(positions.start) as u32, ); let mut end = DisplayPoint::new( row, - layed_out_line.closest_index_for_x(positions.end) as u32, + laid_out_line.closest_index_for_x(positions.end) as u32, ); if end <= start { if start.column() == map.line_len(start.row()) { @@ -218,7 +218,7 @@ pub fn visual_block_motion( } } - if positions.start <= layed_out_line.width { + if positions.start <= laid_out_line.width { let selection = Selection { id: s.new_selection_id(), start: start.to_point(map), diff --git a/crates/welcome/src/base_keymap_setting.rs b/crates/welcome/src/base_keymap_setting.rs index e05a16c350d4de2a7f1c0ddf56b7b581ef7d6b17..54af63007af38b7796b92a9280aa951056fc6f52 100644 --- a/crates/welcome/src/base_keymap_setting.rs +++ b/crates/welcome/src/base_keymap_setting.rs @@ -4,7 +4,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::Settings; -/// Base key bindings scheme. Base keymaps can be overriden with user keymaps. +/// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// /// Default: VSCode #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a1f3e6992aef51291fc66b9f4092e3dbd24c7be6..3e88469aa8593967e3ffea1cfd3ba0392f7e11f7 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1485,7 +1485,7 @@ impl Pane { .child( div() .min_w_6() - // HACK: This empty child is currently necessary to force the drop traget to appear + // HACK: This empty child is currently necessary to force the drop target to appear // despite us setting a min width above. .child("") .h_full() diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1d06db5de3b459b289d3b0392c3fb91ac594a760..e8589849f14dbe9148ff467480f5d4a5583d1ed7 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2617,7 +2617,7 @@ impl Workspace { // If the item belongs to a particular project, then it should // only be included if this project is shared, and the follower - // is in thie project. + // is in the project. // // Some items, like channel notes, do not belong to a particular // project, so they should be included regardless of whether the diff --git a/docs/old/building-zed.md b/docs/old/building-zed.md index ec4538cf85f47d99ee11b24bb876b602eba8249e..79db4a36c9ec65fa54807fa2234ee8190729f067 100644 --- a/docs/old/building-zed.md +++ b/docs/old/building-zed.md @@ -36,7 +36,7 @@ Expect this to take 30min to an hour! Some of these steps will take quite a whil Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - Keep the token in the browser tab/editor for the next two steps -1. (Optional but reccomended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` +1. (Optional but recommended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` 1. Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies: ``` cd .. diff --git a/docs/old/zed/syntax-highlighting.md b/docs/old/zed/syntax-highlighting.md index d4331ee367934453b19c6cd30af57924409b34d7..846bf968764f7dcf16210c7f740ee8a499cc53c4 100644 --- a/docs/old/zed/syntax-highlighting.md +++ b/docs/old/zed/syntax-highlighting.md @@ -4,7 +4,7 @@ This doc is a work in progress! ## Defining syntax highlighting rules -We use tree-sitter queries to match certian properties to highlight. +We use tree-sitter queries to match certain properties to highlight. ### Simple Example: diff --git a/docs/src/configuring_zed.md b/docs/src/configuring_zed.md index 9b9205f70ca7cac1cb587a2800de6af3f7bed956..46f0d35becb63d6b86ace12cc37bea2b8aaee297 100644 --- a/docs/src/configuring_zed.md +++ b/docs/src/configuring_zed.md @@ -4,7 +4,7 @@ Folder-specific settings are used to override Zed's global settings for files within a specific directory in the project panel. To get started, create a `.zed` subdirectory and add a `settings.json` within it. It should be noted that folder-specific settings don't need to live only a project's root, but can be defined at multiple levels in the project hierarchy. In setups like this, Zed will find the configuration nearest to the file you are working in and apply those settings to it. In most cases, this level of flexibility won't be needed and a single configuration for all files in a project is all that is required; the `Zed > Settings > Open Local Settings` menu action is built for this case. Running this action will look for a `.zed/settings.json` file at the root of the first top-level directory in your project panel. If it does not exist, it will create it. -The following global settings can be overriden with a folder-specific configuration: +The following global settings can be overridden with a folder-specific configuration: - `copilot` - `enable_language_server` diff --git a/docs/src/developing_zed__adding_languages.md b/docs/src/developing_zed__adding_languages.md index 2917b08422c1e2153a38f5c857a9318a9228c031..7fce7e85446ec355835bf137f41669bfea1115db 100644 --- a/docs/src/developing_zed__adding_languages.md +++ b/docs/src/developing_zed__adding_languages.md @@ -8,7 +8,7 @@ Zed uses the [Language Server Protocol](https://microsoft.github.io/language-ser ### Defining syntax highlighting rules -We use tree-sitter queries to match certian properties to highlight. +We use tree-sitter queries to match certain properties to highlight. #### Simple Example: diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index 7606e369d05cf02226ea5b783f4ff8369a661be3..7d1b40f924e6637960a55eee7dbb99f0dbedc038 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -39,7 +39,7 @@ Expect this to take 30min to an hour! Some of these steps will take quite a whil Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos - Keep the token in the browser tab/editor for the next two steps -1. (Optional but reccomended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` +1. (Optional but recommended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken` 1. Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies: ``` cd .. From 9a7d2e3fe4065a4611eb3311d6721edf599d2683 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 14:44:10 -0800 Subject: [PATCH 281/334] fmt --- crates/vim/src/visual.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 0f29101d974f26e9c714ac47c1ade3331950b444..ad6486ab0c5157f5b860b793e4809e7cd2e8f1a4 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -206,10 +206,8 @@ pub fn visual_block_motion( row, laid_out_line.closest_index_for_x(positions.start) as u32, ); - let mut end = DisplayPoint::new( - row, - laid_out_line.closest_index_for_x(positions.end) as u32, - ); + let mut end = + DisplayPoint::new(row, laid_out_line.closest_index_for_x(positions.end) as u32); if end <= start { if start.column() == map.line_len(start.row()) { end = start; From 285f4d1be9ed847d94d5f1126c37284e8b0e1c84 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 14:57:13 -0800 Subject: [PATCH 282/334] Fix busted test --- crates/semantic_index/src/semantic_index_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 86eb6b84041fced41d1a0c433ebf62d10634c58f..9da92a15a8acf6dd6ae8bf06d1846fc5073ffee1 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -626,7 +626,7 @@ async fn test_code_context_retrieval_lua() { -- Any functions this instance does not know of will 'look up' to the superclass definition. setmetatable(instance, { __index = classdef, __newindex = mastertable }) return instance - end"#.unindent(), 809), + end"#.unindent(), 810), ] ); } From e6ca92ffa4e5cb88c438d048d7f729bc1df327f8 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 14:58:58 -0800 Subject: [PATCH 283/334] Fix a few more typos --- CONTRIBUTING.md | 2 +- crates/collab/src/rpc.rs | 2 +- crates/gpui/src/app.rs | 4 ++-- crates/gpui/src/elements/list.rs | 2 +- crates/gpui/src/view.rs | 2 +- crates/vim/src/vim.rs | 2 +- docs/old/release-process.md | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c45475e4ca8ccbfbd1b22b9bc6270ca34c28cd3..a85be833214d83d9cd6dcb24d6a41d3974a56595 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,4 +54,4 @@ We're happy to pair with you to help you learn the codebase and get your contrib Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming. We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed. -Remeber that smaller, incremental PRs are easier to review and merge than large PRs. +Remember that smaller, incremental PRs are easier to review and merge than large PRs. diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 9406b4938a8f2795a89cd8f10238964710eb61f3..f9218e5634cdd0d3c104d890fc4d7f3396bb1b06 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3116,7 +3116,7 @@ async fn leave_channel_chat(request: proto::LeaveChannelChat, session: Session) Ok(()) } -/// Retrive the chat history for a channel +/// Retrieve the chat history for a channel async fn get_channel_messages( request: proto::GetChannelMessages, response: Response, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index ab9b4d9f86417c13127de5a8758297c8d4ce0028..8f7345ae16ced7e84cb145de81635b8c805e3fe6 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -967,8 +967,8 @@ impl AppContext { } /// Register a callback to be invoked when a keystroke is received by the application - /// in any window. Note that this fires after all other action and event mechansims have resolved - /// and that this API will not be invoked if the event's propogation is stopped. + /// in any window. Note that this fires after all other action and event mechanisms have resolved + /// and that this API will not be invoked if the event's propagation is stopped. pub fn observe_keystrokes( &mut self, f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static, diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index c0874a8dd4116275846edc24c1ebcfcfd9d752b6..2921d90a10088d8a4f03018b6f29f8d2416e110a 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -615,7 +615,7 @@ mod test { // Reset state.reset(5); - // And then recieve a scroll event _before_ the next paint + // And then receive a scroll event _before_ the next paint cx.simulate_event(ScrollWheelEvent { position: point(px(1.), px(1.)), delta: ScrollDelta::Pixels(point(px(0.), px(-500.))), diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 3701bbbd69419daff04e83fdc85413099d34f42f..6426ac4c32f05e0765f2b5ca3cdb81ee603edc6e 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -200,7 +200,7 @@ impl PartialEq for WeakView { impl Eq for WeakView {} -/// A dynically-typed handle to a view, which can be downcast to a [View] for a specific type. +/// A dynamically-typed handle to a view, which can be downcast to a [View] for a specific type. #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index e03efb0a64b4b55e92ed8d92d8837351ac573e0d..0cb038807bf44be70dbd296276f669a0c69bff63 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -332,7 +332,7 @@ impl Vim { } } - /// Explicitly record one action (equiavlent to start_recording and stop_recording) + /// Explicitly record one action (equivalents to start_recording and stop_recording) pub fn record_current_action(&mut self, cx: &mut WindowContext) { self.start_recording(cx); self.stop_recording(); diff --git a/docs/old/release-process.md b/docs/old/release-process.md index 6162304a7b0a74fe3bfad2cc110d9fbc6a820c51..fc237d959069188d26ea85c7417a3a079377997b 100644 --- a/docs/old/release-process.md +++ b/docs/old/release-process.md @@ -90,7 +90,7 @@ This means that when releasing a new version of Zed that has changes to the RPC 1. If needing a migration: - First check that the migration is valid. The database serves both preview and stable simultaneously, so new columns need to have defaults and old tables or columns can't be dropped. - Then use `script/deploy-migration` (production, staging, preview, nightly). ex: `script/deploy-migration preview 0.19.0` - - If there is an 'Error: container is waiting to start', you can review logs manually with: `kubectl --namespace logs ` to make sure the mgiration ran successfully. + - If there is an 'Error: container is waiting to start', you can review logs manually with: `kubectl --namespace logs ` to make sure the mgiration ran successfully. 1. Once that CI job completes, you will be able to run the following command to deploy that docker image. The script takes two arguments: an environment (`production`, `preview`, or `staging`), and a version number (e.g. `0.10.1`): ``` From e42a9ac2f103bf224ed74e5844b20845b47108cf Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 15:22:37 -0800 Subject: [PATCH 284/334] Add typos configuration for zed and add a few more typo fixes --- crates/collab/src/tests/following_tests.rs | 7 ++++++- crates/search/src/history.rs | 2 +- typos.toml | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 typos.toml diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index af184d7d02a3458deddb7bfdbf1f77be48d70790..b3af077e9d177ba3a3580fe4ea2a8c92523aeac1 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1735,6 +1735,11 @@ async fn test_following_into_excluded_file( vec![18..17] ); + editor_for_excluded_a.update(cx_a, |editor, cx| { + editor.select_right(&Default::default(), cx); + }); + executor.run_until_parked(); + // Changes from B to the excluded file are replicated in A's editor editor_for_excluded_b.update(cx_b, |editor, cx| { editor.handle_input("\nCo-Authored-By: B ", cx); @@ -1743,7 +1748,7 @@ async fn test_following_into_excluded_file( editor_for_excluded_a.update(cx_a, |editor, cx| { assert_eq!( editor.text(cx), - "new commit messag\nCo-Authored-By: B " + "new commit message\nCo-Authored-By: B " ); }); } diff --git a/crates/search/src/history.rs b/crates/search/src/history.rs index 6b06c60293d4389693b9d3692a2649856076081f..5571313acb280c143b70e05fb9c5f8aa066bf56e 100644 --- a/crates/search/src/history.rs +++ b/crates/search/src/history.rs @@ -85,7 +85,7 @@ mod tests { assert_eq!( search_history.current(), None, - "No current selection should be set fo the default search history" + "No current selection should be set for the default search history" ); search_history.add("rust".to_string()); diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000000000000000000000000000000000000..47eb2a0d9c493d4333d808310c99c2632f55e913 --- /dev/null +++ b/typos.toml @@ -0,0 +1,19 @@ +[files] +ignore-files = true +extend-exclude = [ + # Vim makes heavy use of partial typing tables + "crates/vim/*", + # glsl isn't recognized by this tool + "crates/zed/src/languages/glsl/*", + # File suffixes aren't typos + "assets/icons/file_icons/file_types.json", + # :/ + "crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql", + # Editor and file finder rely on partial typing and custom in-string syntax + "crates/file_finder/src/file_finder.rs", + "crates/editor/src/editor_tests.rs", +] + +[default] +extend-ignore-re = ["ba"] +check-filename = true \ No newline at end of file From e3e3ef528e71f032a3d90535c283c601a33b1d14 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 15:28:25 -0800 Subject: [PATCH 285/334] Add typos ci --- .github/actions/check_style/action.yml | 3 +++ .github/workflows/ci.yml | 3 +-- crates/gpui/src/gpui.rs | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 25020e4e4c5399026c1ab32622903a3779ba86b2..290496d7e7866490286fafeb31d867b73c20ab80 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -21,3 +21,6 @@ runs: run: | export SQUAWK_GITHUB_TOKEN=${{ github.token }} . ./script/squawk + + - name: Run spelling check + uses: crate-ci/typos@master diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 476f263997d5caa7fb0f3cb576397b618347e82f..7b9ca8de8e015de0161a1be1c8fddb1f3fa9d825 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ env: jobs: style: - name: Check formatting and Clippy lints + name: Check formatting, Clippy lints, and spelling runs-on: - self-hosted - test @@ -40,7 +40,6 @@ jobs: - name: Run style checks uses: ./.github/actions/check_style - tests: name: Run tests runs-on: diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 6f5e30149d9691b3c364d62ab2e3ce6ec7da1b4c..1c7e7432889b2a20ba2c3bfaf850581c8c323a5c 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -88,6 +88,7 @@ use std::{ }; use taffy::TaffyLayoutEngine; +/// Here's a spelling mistake: visibile pub trait Context { type Result; From 6cbc49e5f016f25223d1567d84f7f8c99bfada8f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 18 Jan 2024 00:48:37 +0100 Subject: [PATCH 286/334] Editor docs (#4097) Release Notes: - N/A --------- Co-authored-by: Kirill --- crates/assistant/src/assistant_panel.rs | 17 +- crates/collab/src/tests/editor_tests.rs | 12 +- crates/collab/src/tests/following_tests.rs | 4 +- crates/copilot_ui/src/copilot_button.rs | 2 +- crates/diagnostics/src/diagnostics.rs | 2 +- crates/diagnostics/src/items.rs | 2 +- crates/editor/src/actions.rs | 218 ++++++++++++++ crates/editor/src/display_map.rs | 14 +- crates/editor/src/display_map/block_map.rs | 2 +- crates/editor/src/display_map/fold_map.rs | 2 +- crates/editor/src/display_map/inlay_map.rs | 6 +- crates/editor/src/display_map/wrap_map.rs | 2 +- crates/editor/src/editor.rs | 273 +++--------------- crates/editor/src/element.rs | 2 +- crates/editor/src/inlay_hint_cache.rs | 59 +++- crates/editor/src/link_go_to_definition.rs | 2 +- crates/editor/src/movement.rs | 72 ++++- crates/editor/src/scroll.rs | 13 +- crates/editor/src/scroll/autoscroll.rs | 2 +- crates/editor/src/selections_collection.rs | 14 +- crates/file_finder/src/file_finder.rs | 2 +- crates/go_to_line/src/go_to_line.rs | 2 +- crates/journal/src/journal.rs | 2 +- crates/language_tools/src/lsp_log.rs | 2 +- crates/language_tools/src/syntax_tree_view.rs | 2 +- crates/outline/src/outline.rs | 4 +- crates/project_panel/src/project_panel.rs | 2 +- crates/project_symbols/src/project_symbols.rs | 2 +- .../quick_action_bar/src/quick_action_bar.rs | 4 +- crates/search/src/buffer_search.rs | 4 +- crates/search/src/project_search.rs | 8 +- .../src/stories/auto_height_editor.rs | 6 +- crates/terminal_view/src/terminal_view.rs | 4 +- crates/vim/src/command.rs | 32 +- crates/vim/src/insert.rs | 2 +- crates/vim/src/motion.rs | 5 +- crates/vim/src/normal.rs | 2 +- crates/vim/src/normal/case.rs | 2 +- crates/vim/src/normal/change.rs | 7 +- crates/vim/src/normal/delete.rs | 2 +- crates/vim/src/normal/increment.rs | 2 +- crates/vim/src/normal/paste.rs | 3 +- crates/vim/src/normal/repeat.rs | 2 +- crates/vim/src/normal/scroll.rs | 2 +- crates/vim/src/object.rs | 5 +- crates/vim/src/visual.rs | 2 +- crates/zed/src/app_menus.rs | 49 ++-- crates/zed/src/open_listener.rs | 2 +- crates/zed/src/zed.rs | 2 +- 49 files changed, 508 insertions(+), 377 deletions(-) create mode 100644 crates/editor/src/actions.rs diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 241b9af923e44da0139bf07d3a57631c6c7e353b..20d5efaaa868b07929916bf8c6dddc12f5a22520 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -19,12 +19,13 @@ use chrono::{DateTime, Local}; use client::telemetry::AssistantKind; use collections::{hash_map, HashMap, HashSet, VecDeque}; use editor::{ + actions::{MoveDown, MoveUp}, display_map::{ BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, ToDisplayPoint, }, - scroll::autoscroll::{Autoscroll, AutoscrollStrategy}, - Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MoveDown, MoveUp, MultiBufferSnapshot, - ToOffset, ToPoint, + scroll::{Autoscroll, AutoscrollStrategy}, + Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBufferSnapshot, ToOffset, + ToPoint, }; use fs::Fs; use futures::StreamExt; @@ -479,7 +480,7 @@ impl AssistantPanel { fn cancel_last_inline_assist( workspace: &mut Workspace, - _: &editor::Cancel, + _: &editor::actions::Cancel, cx: &mut ViewContext, ) { if let Some(panel) = workspace.panel::(cx) { @@ -891,7 +892,7 @@ impl AssistantPanel { } } - fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext) { + fn handle_editor_cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext) { if let Some(search_bar) = self.toolbar.read(cx).item_of_type::() { if !search_bar.read(cx).is_dismissed() { search_bar.update(cx, |search_bar, cx| { @@ -2158,7 +2159,7 @@ impl ConversationEditor { } } - fn cancel_last_assist(&mut self, _: &editor::Cancel, cx: &mut ViewContext) { + fn cancel_last_assist(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext) { if !self .conversation .update(cx, |conversation, _| conversation.cancel_last_assist()) @@ -2417,7 +2418,7 @@ impl ConversationEditor { } } - fn copy(&mut self, _: &editor::Copy, cx: &mut ViewContext) { + fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext) { let editor = self.editor.read(cx); let conversation = self.conversation.read(cx); if editor.selections.count() == 1 { @@ -2828,7 +2829,7 @@ impl InlineAssistant { cx.notify(); } - fn cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext) { + fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext) { cx.emit(InlineAssistantEvent::Canceled); } diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index a5fa187d24acc93af3b2ff64dbf2ef96fffa7ea3..6f1ad6e4a335e65c58aa20ac19a859f0e849b068 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -8,9 +8,11 @@ use std::{ use call::ActiveCall; use editor::{ + actions::{ + ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Redo, Rename, ToggleCodeActions, Undo, + }, test::editor_test_context::{AssertionContextManager, EditorTestContext}, - ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, - Undo, + Editor, }; use futures::StreamExt; use gpui::{TestAppContext, VisualContext, VisualTestContext}; @@ -217,7 +219,8 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( editor_cx_b.set_selections_state(indoc! {" Some textˇ "}); - editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx)); + editor_cx_a + .update_editor(|editor, cx| editor.newline_above(&editor::actions::NewlineAbove, cx)); executor.run_until_parked(); editor_cx_a.assert_editor_state(indoc! {" ˇ @@ -237,7 +240,8 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( Some textˇ "}); - editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx)); + editor_cx_a + .update_editor(|editor, cx| editor.newline_below(&editor::actions::NewlineBelow, cx)); executor.run_until_parked(); editor_cx_a.assert_editor_state(indoc! {" diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index af184d7d02a3458deddb7bfdbf1f77be48d70790..d3565eb7ca32797a65db84a3bf3878db5caa771a 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1229,7 +1229,9 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont }); // When client B moves, it automatically stops following client A. - editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx)); + editor_b2.update(cx_b, |editor, cx| { + editor.move_right(&editor::actions::MoveRight, cx) + }); assert_eq!( workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)), None diff --git a/crates/copilot_ui/src/copilot_button.rs b/crates/copilot_ui/src/copilot_button.rs index e5a1a942358a20c72fbb1037413796aeb84be77a..9dc4e75cb1cced8c5097bb3b7ee474fba9ed2249 100644 --- a/crates/copilot_ui/src/copilot_button.rs +++ b/crates/copilot_ui/src/copilot_button.rs @@ -1,7 +1,7 @@ use crate::sign_in::CopilotCodeVerification; use anyhow::Result; use copilot::{Copilot, SignOut, Status}; -use editor::{scroll::autoscroll::Autoscroll, Editor}; +use editor::{scroll::Autoscroll, Editor}; use fs::Fs; use gpui::{ div, Action, AnchorCorner, AppContext, AsyncWindowContext, Entity, IntoElement, ParentElement, diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index ca701e626e7f02284e22f553b507ca55a09524a5..8504d3de5e6ff6a2531f7a146db230ee9e840d14 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -8,7 +8,7 @@ use editor::{ diagnostic_block_renderer, display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock}, highlight_diagnostic_message, - scroll::autoscroll::Autoscroll, + scroll::Autoscroll, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset, }; use futures::future::try_join_all; diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 462718c0f345268131cd04867d2bff9f48a451a0..d823ad52afc271e6789dd6f16724cc74c70ab227 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -80,7 +80,7 @@ impl Render for DiagnosticIndicator { Button::new("diagnostic_message", message) .label_size(LabelSize::Small) .tooltip(|cx| { - Tooltip::for_action("Next Diagnostic", &editor::GoToDiagnostic, cx) + Tooltip::for_action("Next Diagnostic", &editor::actions::GoToDiagnostic, cx) }) .on_click(cx.listener(|this, _, cx| { this.go_to_next_diagnostic(cx); diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs new file mode 100644 index 0000000000000000000000000000000000000000..9532bb642d85b15ae5cd8edf68e2338b1cefa174 --- /dev/null +++ b/crates/editor/src/actions.rs @@ -0,0 +1,218 @@ +//! This module contains all actions supported by [`Editor`]. +use super::*; + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct SelectNext { + #[serde(default)] + pub replace_newest: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct SelectPrevious { + #[serde(default)] + pub replace_newest: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct SelectAllMatches { + #[serde(default)] + pub replace_newest: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct SelectToBeginningOfLine { + #[serde(default)] + pub(super) stop_at_soft_wraps: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct MovePageUp { + #[serde(default)] + pub(super) center_cursor: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct MovePageDown { + #[serde(default)] + pub(super) center_cursor: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct SelectToEndOfLine { + #[serde(default)] + pub(super) stop_at_soft_wraps: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct ToggleCodeActions { + #[serde(default)] + pub deployed_from_indicator: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct ConfirmCompletion { + #[serde(default)] + pub item_ix: Option, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct ConfirmCodeAction { + #[serde(default)] + pub item_ix: Option, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct ToggleComments { + #[serde(default)] + pub advance_downwards: bool, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct FoldAt { + pub buffer_row: u32, +} + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct UnfoldAt { + pub buffer_row: u32, +} +impl_actions!( + editor, + [ + SelectNext, + SelectPrevious, + SelectAllMatches, + SelectToBeginningOfLine, + MovePageUp, + MovePageDown, + SelectToEndOfLine, + ToggleCodeActions, + ConfirmCompletion, + ConfirmCodeAction, + ToggleComments, + FoldAt, + UnfoldAt + ] +); + +gpui::actions!( + editor, + [ + AddSelectionAbove, + AddSelectionBelow, + Backspace, + Cancel, + ConfirmRename, + ContextMenuFirst, + ContextMenuLast, + ContextMenuNext, + ContextMenuPrev, + ConvertToKebabCase, + ConvertToLowerCamelCase, + ConvertToLowerCase, + ConvertToSnakeCase, + ConvertToTitleCase, + ConvertToUpperCamelCase, + ConvertToUpperCase, + Copy, + CopyHighlightJson, + CopyPath, + CopyRelativePath, + Cut, + CutToEndOfLine, + Delete, + DeleteLine, + DeleteToBeginningOfLine, + DeleteToEndOfLine, + DeleteToNextSubwordEnd, + DeleteToNextWordEnd, + DeleteToPreviousSubwordStart, + DeleteToPreviousWordStart, + DuplicateLine, + ExpandMacroRecursively, + FindAllReferences, + Fold, + FoldSelectedRanges, + Format, + GoToDefinition, + GoToDefinitionSplit, + GoToDiagnostic, + GoToHunk, + GoToPrevDiagnostic, + GoToPrevHunk, + GoToTypeDefinition, + GoToTypeDefinitionSplit, + HalfPageDown, + HalfPageUp, + Hover, + Indent, + JoinLines, + LineDown, + LineUp, + MoveDown, + MoveLeft, + MoveLineDown, + MoveLineUp, + MoveRight, + MoveToBeginning, + MoveToBeginningOfLine, + MoveToEnclosingBracket, + MoveToEnd, + MoveToEndOfLine, + MoveToEndOfParagraph, + MoveToNextSubwordEnd, + MoveToNextWordEnd, + MoveToPreviousSubwordStart, + MoveToPreviousWordStart, + MoveToStartOfParagraph, + MoveUp, + Newline, + NewlineAbove, + NewlineBelow, + NextScreen, + OpenExcerpts, + Outdent, + PageDown, + PageUp, + Paste, + Redo, + RedoSelection, + Rename, + RestartLanguageServer, + RevealInFinder, + ReverseLines, + ScrollCursorBottom, + ScrollCursorCenter, + ScrollCursorTop, + SelectAll, + SelectDown, + SelectLargerSyntaxNode, + SelectLeft, + SelectLine, + SelectRight, + SelectSmallerSyntaxNode, + SelectToBeginning, + SelectToEnd, + SelectToEndOfParagraph, + SelectToNextSubwordEnd, + SelectToNextWordEnd, + SelectToPreviousSubwordStart, + SelectToPreviousWordStart, + SelectToStartOfParagraph, + SelectUp, + ShowCharacterPalette, + ShowCompletions, + ShuffleLines, + SortLinesCaseInsensitive, + SortLinesCaseSensitive, + SplitSelectionIntoLines, + Tab, + TabPrev, + ToggleInlayHints, + ToggleSoftWrap, + Transpose, + Undo, + UndoSelection, + UnfoldLines, + ] +); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4f2d5179dbd08fedd38f46ea23f919d6c30147c8..7ab5b0ff2abe75b6a35ab2ed120c2dee55e489af 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -30,7 +30,8 @@ pub use block_map::{ }; pub use self::fold_map::{Fold, FoldPoint}; -pub use self::inlay_map::{Inlay, InlayOffset, InlayPoint}; +pub use self::inlay_map::{InlayOffset, InlayPoint}; +pub(crate) use inlay_map::Inlay; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { @@ -220,7 +221,7 @@ impl DisplayMap { .insert(Some(type_id), Arc::new((style, ranges))); } - pub fn highlight_inlays( + pub(crate) fn highlight_inlays( &mut self, type_id: TypeId, highlights: Vec, @@ -258,11 +259,11 @@ impl DisplayMap { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } - pub fn current_inlays(&self) -> impl Iterator { + pub(crate) fn current_inlays(&self) -> impl Iterator { self.inlay_map.current_inlays() } - pub fn splice_inlays( + pub(crate) fn splice_inlays( &mut self, to_remove: Vec, to_insert: Vec, @@ -306,7 +307,7 @@ impl DisplayMap { } #[derive(Debug, Default)] -pub struct Highlights<'a> { +pub(crate) struct Highlights<'a> { pub text_highlights: Option<&'a TextHighlights>, pub inlay_highlights: Option<&'a InlayHighlights>, pub inlay_highlight_style: Option, @@ -880,8 +881,9 @@ impl DisplaySnapshot { self.text_highlights.get(&Some(type_id)).cloned() } + #[allow(unused)] #[cfg(any(test, feature = "test-support"))] - pub fn inlay_highlights( + pub(crate) fn inlay_highlights( &self, ) -> Option<&HashMap> { let type_id = TypeId::of::(); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 6eb0d05bfe84c4b487bb65bfc97ee49bcaff1736..dbbcbccb6e52b1596c408fb45d82c4798a871f46 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -582,7 +582,7 @@ impl BlockSnapshot { .collect() } - pub fn chunks<'a>( + pub(crate) fn chunks<'a>( &'a self, rows: Range, language_aware: bool, diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 4dad2d52aeb5236ec2936b8bdcaf3b06f760cc84..7c6eeb444eb69ca7557a6d55ab4b4b1125b8b078 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -655,7 +655,7 @@ impl FoldSnapshot { } } - pub fn chunks<'a>( + pub(crate) fn chunks<'a>( &'a self, range: Range, language_aware: bool, diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 84fad96a48026c4ab7a08f3f6674a0a2958b89a7..c0d5198ddd9734c5a22cde9ee22e9ca837c23ebc 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -35,8 +35,8 @@ enum Transform { } #[derive(Debug, Clone)] -pub struct Inlay { - pub id: InlayId, +pub(crate) struct Inlay { + pub(crate) id: InlayId, pub position: Anchor, pub text: text::Rope, } @@ -1016,7 +1016,7 @@ impl InlaySnapshot { (line_end - line_start) as u32 } - pub fn chunks<'a>( + pub(crate) fn chunks<'a>( &'a self, range: Range, language_aware: bool, diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index ce2e5ee3d9eb66c1e4a769dacdb9ff8c357a1ff3..39f9a2315bd928e0d6b2a67a262957911d586cbd 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -568,7 +568,7 @@ impl WrapSnapshot { Patch::new(wrap_edits) } - pub fn chunks<'a>( + pub(crate) fn chunks<'a>( &'a self, rows: Range, language_aware: bool, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 30b0a73d37e093bcb456d4fa6cf8e0c2ff98d5ff..378607a4fb0633695aaaaa3c7df630eb1ed41073 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1,3 +1,18 @@ +#![allow(rustdoc::private_intra_doc_links)] +//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise). +//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element. +//! It comes in different flavors: single line, multiline and a fixed height one. +//! +//! Editor contains of multiple large submodules: +//! * [`element`] — the place where all rendering happens +//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them. +//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.). +//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly. +//! +//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s). +//! +//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides it's behaviour. +pub mod actions; mod blink_manager; pub mod display_map; mod editor_settings; @@ -14,13 +29,14 @@ pub mod movement; mod persistence; mod rust_analyzer_ext; pub mod scroll; -pub mod selections_collection; +mod selections_collection; #[cfg(test)] mod editor_tests; #[cfg(any(test, feature = "test-support"))] pub mod test; use ::git::diff::DiffHunk; +pub(crate) use actions::*; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context as _, Result}; use blink_manager::BlinkManager; @@ -32,14 +48,13 @@ use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; pub use editor_settings::EditorSettings; -pub use element::{ - Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles, -}; +use element::LineWithInvisibles; +pub use element::{Cursor, EditorElement, HighlightedRange, HighlightedRangeLine}; use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use git::diff_hunk_to_display; use gpui::{ - actions, div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action, + div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton, @@ -51,7 +66,7 @@ use hover_popover::{hide_hover, HoverState}; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; -pub use language::{char_kind, CharKind}; +use language::{char_kind, CharKind}; use language::{ language_settings::{self, all_language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction, @@ -74,9 +89,7 @@ use parking_lot::RwLock; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::prelude::*; use rpc::proto::{self, *}; -use scroll::{ - autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, -}; +use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide}; use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; @@ -113,10 +126,12 @@ const MAX_LINE_LEN: usize = 1024; const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10; const MAX_SELECTION_HISTORY_LEN: usize = 1024; const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); +#[doc(hidden)] pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250); +#[doc(hidden)] pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75); -pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); +pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2); pub fn render_parsed_markdown( element_id: impl Into, @@ -181,103 +196,8 @@ pub fn render_parsed_markdown( }) } -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct SelectNext { - #[serde(default)] - pub replace_newest: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct SelectPrevious { - #[serde(default)] - pub replace_newest: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct SelectAllMatches { - #[serde(default)] - pub replace_newest: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct SelectToBeginningOfLine { - #[serde(default)] - stop_at_soft_wraps: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct MovePageUp { - #[serde(default)] - center_cursor: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct MovePageDown { - #[serde(default)] - center_cursor: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct SelectToEndOfLine { - #[serde(default)] - stop_at_soft_wraps: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct ToggleCodeActions { - #[serde(default)] - pub deployed_from_indicator: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct ConfirmCompletion { - #[serde(default)] - pub item_ix: Option, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct ConfirmCodeAction { - #[serde(default)] - pub item_ix: Option, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct ToggleComments { - #[serde(default)] - pub advance_downwards: bool, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct FoldAt { - pub buffer_row: u32, -} - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct UnfoldAt { - pub buffer_row: u32, -} - -impl_actions!( - editor, - [ - SelectNext, - SelectPrevious, - SelectAllMatches, - SelectToBeginningOfLine, - MovePageUp, - MovePageDown, - SelectToEndOfLine, - ToggleCodeActions, - ConfirmCompletion, - ConfirmCodeAction, - ToggleComments, - FoldAt, - UnfoldAt - ] -); - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum InlayId { +pub(crate) enum InlayId { Suggestion(usize), Hint(usize), } @@ -291,128 +211,6 @@ impl InlayId { } } -actions!( - editor, - [ - AddSelectionAbove, - AddSelectionBelow, - Backspace, - Cancel, - ConfirmRename, - ContextMenuFirst, - ContextMenuLast, - ContextMenuNext, - ContextMenuPrev, - ConvertToKebabCase, - ConvertToLowerCamelCase, - ConvertToLowerCase, - ConvertToSnakeCase, - ConvertToTitleCase, - ConvertToUpperCamelCase, - ConvertToUpperCase, - Copy, - CopyHighlightJson, - CopyPath, - CopyRelativePath, - Cut, - CutToEndOfLine, - Delete, - DeleteLine, - DeleteToBeginningOfLine, - DeleteToEndOfLine, - DeleteToNextSubwordEnd, - DeleteToNextWordEnd, - DeleteToPreviousSubwordStart, - DeleteToPreviousWordStart, - DuplicateLine, - ExpandMacroRecursively, - FindAllReferences, - Fold, - FoldSelectedRanges, - Format, - GoToDefinition, - GoToDefinitionSplit, - GoToDiagnostic, - GoToHunk, - GoToPrevDiagnostic, - GoToPrevHunk, - GoToTypeDefinition, - GoToTypeDefinitionSplit, - HalfPageDown, - HalfPageUp, - Hover, - Indent, - JoinLines, - LineDown, - LineUp, - MoveDown, - MoveLeft, - MoveLineDown, - MoveLineUp, - MoveRight, - MoveToBeginning, - MoveToBeginningOfLine, - MoveToEnclosingBracket, - MoveToEnd, - MoveToEndOfLine, - MoveToEndOfParagraph, - MoveToNextSubwordEnd, - MoveToNextWordEnd, - MoveToPreviousSubwordStart, - MoveToPreviousWordStart, - MoveToStartOfParagraph, - MoveUp, - Newline, - NewlineAbove, - NewlineBelow, - NextScreen, - OpenExcerpts, - Outdent, - PageDown, - PageUp, - Paste, - Redo, - RedoSelection, - Rename, - RestartLanguageServer, - RevealInFinder, - ReverseLines, - ScrollCursorBottom, - ScrollCursorCenter, - ScrollCursorTop, - SelectAll, - SelectDown, - SelectLargerSyntaxNode, - SelectLeft, - SelectLine, - SelectRight, - SelectSmallerSyntaxNode, - SelectToBeginning, - SelectToEnd, - SelectToEndOfParagraph, - SelectToNextSubwordEnd, - SelectToNextWordEnd, - SelectToPreviousSubwordStart, - SelectToPreviousWordStart, - SelectToStartOfParagraph, - SelectUp, - ShowCharacterPalette, - ShowCompletions, - ShuffleLines, - SortLinesCaseInsensitive, - SortLinesCaseSensitive, - SplitSelectionIntoLines, - Tab, - TabPrev, - ToggleInlayHints, - ToggleSoftWrap, - Transpose, - Undo, - UndoSelection, - UnfoldLines, - ] -); - enum DocumentHighlightRead {} enum DocumentHighlightWrite {} enum InputComposition {} @@ -489,7 +287,7 @@ pub enum SelectPhase { } #[derive(Clone, Debug)] -pub enum SelectMode { +pub(crate) enum SelectMode { Character, Word(Range), Line(Range), @@ -760,6 +558,7 @@ struct SnippetState { active_index: usize, } +#[doc(hidden)] pub struct RenameState { pub range: Range, pub old_name: Arc, @@ -1499,7 +1298,7 @@ impl CodeActionsMenu { } } -pub struct CopilotState { +pub(crate) struct CopilotState { excerpt_id: Option, pending_refresh: Task>, pending_cycling_refresh: Task>, @@ -1619,15 +1418,13 @@ pub struct ClipboardSelection { } #[derive(Debug)] -pub struct NavigationData { +pub(crate) struct NavigationData { cursor_anchor: Anchor, cursor_position: Point, scroll_anchor: ScrollAnchor, scroll_top_row: u32, } -pub struct EditorCreated(pub View); - enum GotoDefinitionKind { Symbol, Type, @@ -8125,7 +7922,7 @@ impl Editor { } } - pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext) { + pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext) { let mut fold_ranges = Vec::new(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); @@ -8484,7 +8281,7 @@ impl Editor { cx.notify(); } - pub fn highlight_inlay_background( + pub(crate) fn highlight_inlay_background( &mut self, ranges: Vec, color_fetcher: fn(&ThemeColors) -> Hsla, @@ -8691,7 +8488,7 @@ impl Editor { cx.notify(); } - pub fn highlight_inlays( + pub(crate) fn highlight_inlays( &mut self, highlights: Vec, style: HighlightStyle, @@ -9899,7 +9696,7 @@ pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, V (text_without_backticks.into(), code_ranges) } -pub fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla { +fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla { match (severity, valid) { (DiagnosticSeverity::ERROR, true) => colors.error, (DiagnosticSeverity::ERROR, false) => colors.error, @@ -9958,7 +9755,7 @@ pub fn styled_runs_for_code_label<'a>( }) } -pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator + 'a { +pub(crate) fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator + 'a { let mut index = 0; let mut codepoints = text.char_indices().peekable(); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b82bd55bcf5e898299ca8a3a0e1d88cf37c72367..64c0fdeb64045abef5932eb76eff0e33c3a39c4a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2599,7 +2599,7 @@ impl EditorElement { } #[derive(Debug)] -pub struct LineWithInvisibles { +pub(crate) struct LineWithInvisibles { pub line: ShapedLine, invisibles: Vec, } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 59c6b8605c1001440999e3f6909db300cf33e392..73c9b5dcd754f208318db809fcec4c8ba430ba87 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1,3 +1,11 @@ +/// Stores and updates all data received from LSP textDocument/inlayHint requests. +/// Has nothing to do with other inlays, e.g. copilot suggestions — those are stored elsewhere. +/// On every update, cache may query for more inlay hints and update inlays on the screen. +/// +/// Inlays stored on screen are in [`crate::display_map::inlay_map`] and this cache is the only way to update any inlay hint data in the visible hints in the inlay map. +/// For determining the update to the `inlay_map`, the cache requires a list of visible inlay hints — all other hints are not relevant and their separate updates are not influencing the cache work. +/// +/// Due to the way the data is stored for both visible inlays and the cache, every inlay (and inlay hint) collection is editor-specific, so a single buffer may have multiple sets of inlays of open on different panes. use std::{ cmp, ops::{ControlFlow, Range}, @@ -39,7 +47,7 @@ struct TasksForRanges { } #[derive(Debug)] -pub struct CachedExcerptHints { +struct CachedExcerptHints { version: usize, buffer_version: Global, buffer_id: u64, @@ -47,15 +55,30 @@ pub struct CachedExcerptHints { hints_by_id: HashMap, } +/// A logic to apply when querying for new inlay hints and deciding what to do with the old entries in the cache in case of conflicts. #[derive(Debug, Clone, Copy)] -pub enum InvalidationStrategy { +pub(super) enum InvalidationStrategy { + /// Hints reset is requested by the LSP server. + /// Demands to re-query all inlay hints needed and invalidate all cached entries, but does not require instant update with invalidation. + /// + /// Despite nothing forbids language server from sending this request on every edit, it is expected to be sent only when certain internal server state update, invisible for the editor otherwise. RefreshRequested, + /// Multibuffer excerpt(s) and/or singleton buffer(s) were edited at least on one place. + /// Neither editor nor LSP is able to tell which open file hints' are not affected, so all of them have to be invalidated, re-queried and do that fast enough to avoid being slow, but also debounce to avoid loading hints on every fast keystroke sequence. BufferEdited, + /// A new file got opened/new excerpt was added to a multibuffer/a [multi]buffer was scrolled to a new position. + /// No invalidation should be done at all, all new hints are added to the cache. + /// + /// A special case is the settings change: in addition to LSP capabilities, Zed allows omitting certain hint kinds (defined by the corresponding LSP part: type/parameter/other). + /// This does not lead to cache invalidation, but would require cache usage for determining which hints are not displayed and issuing an update to inlays on the screen. None, } -#[derive(Debug, Default)] -pub struct InlaySplice { +/// A splice to send into the `inlay_map` for updating the visible inlays on the screen. +/// "Visible" inlays may not be displayed in the buffer right away, but those are ready to be displayed on further buffer scroll, pane item activations, etc. right away without additional LSP queries or settings changes. +/// The data in the cache is never used directly for displaying inlays on the screen, to avoid races with updates from LSP queries and sync overhead. +/// Splice is picked to help avoid extra hint flickering and "jumps" on the screen. +pub(super) struct InlaySplice { pub to_remove: Vec, pub to_insert: Vec, } @@ -237,7 +260,7 @@ impl TasksForRanges { } impl InlayHintCache { - pub fn new(inlay_hint_settings: InlayHintSettings) -> Self { + pub(super) fn new(inlay_hint_settings: InlayHintSettings) -> Self { Self { allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(), enabled: inlay_hint_settings.enabled, @@ -248,7 +271,10 @@ impl InlayHintCache { } } - pub fn update_settings( + /// Checks inlay hint settings for enabled hint kinds and general enabled state. + /// Generates corresponding inlay_map splice updates on settings changes. + /// Does not update inlay hint cache state on disabling or inlay hint kinds change: only reenabling forces new LSP queries. + pub(super) fn update_settings( &mut self, multi_buffer: &Model, new_hint_settings: InlayHintSettings, @@ -299,7 +325,11 @@ impl InlayHintCache { } } - pub fn spawn_hint_refresh( + /// If needed, queries LSP for new inlay hints, using the invalidation strategy given. + /// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first, + /// followed by the delayed queries of the same range above and below the visible one. + /// This way, concequent refresh invocations are less likely to trigger LSP queries for the invisible ranges. + pub(super) fn spawn_hint_refresh( &mut self, reason: &'static str, excerpts_to_query: HashMap, Global, Range)>, @@ -460,7 +490,11 @@ impl InlayHintCache { } } - pub fn remove_excerpts(&mut self, excerpts_removed: Vec) -> Option { + /// Completely forget of certain excerpts that were removed from the multibuffer. + pub(super) fn remove_excerpts( + &mut self, + excerpts_removed: Vec, + ) -> Option { let mut to_remove = Vec::new(); for excerpt_to_remove in excerpts_removed { self.update_tasks.remove(&excerpt_to_remove); @@ -480,7 +514,7 @@ impl InlayHintCache { } } - pub fn clear(&mut self) { + pub(super) fn clear(&mut self) { if !self.update_tasks.is_empty() || !self.hints.is_empty() { self.version += 1; } @@ -488,7 +522,7 @@ impl InlayHintCache { self.hints.clear(); } - pub fn hint_by_id(&self, excerpt_id: ExcerptId, hint_id: InlayId) -> Option { + pub(super) fn hint_by_id(&self, excerpt_id: ExcerptId, hint_id: InlayId) -> Option { self.hints .get(&excerpt_id)? .read() @@ -516,7 +550,8 @@ impl InlayHintCache { self.version } - pub fn spawn_hint_resolve( + /// Queries a certain hint from the cache for extra data via the LSP resolve request. + pub(super) fn spawn_hint_resolve( &self, buffer_id: u64, excerpt_id: ExcerptId, @@ -1199,7 +1234,7 @@ pub mod tests { use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; use crate::{ - scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount}, + scroll::{scroll_amount::ScrollAmount, Autoscroll}, ExcerptRange, }; use futures::StreamExt; diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 04dcf9301500024230e55e30d8b20ee4b04b2c9f..c4da7fcd38729599e5782e34ca71020e62c5e1e1 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -69,7 +69,7 @@ pub enum GoToDefinitionLink { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct InlayHighlight { +pub(crate) struct InlayHighlight { pub inlay: InlayId, pub inlay_position: Anchor, pub range: Range, diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 72441974c3788d659084c19503ffc22740bfd005..0cf8ac7440ecbba8cd1e841360c69e302ad71b37 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -1,3 +1,6 @@ +//! Movement module contains helper functions for calculating intended position +//! in editor given a given motion (e.g. it handles converting a "move left" command into coordinates in editor). It is exposed mostly for use by vim crate. + use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint}; use gpui::{px, Pixels, TextSystem}; @@ -5,6 +8,9 @@ use language::Point; use std::{ops::Range, sync::Arc}; +/// Defines search strategy for items in `movement` module. +/// `FindRange::SingeLine` only looks for a match on a single line at a time, whereas +/// `FindRange::MultiLine` keeps going until the end of a string. #[derive(Debug, PartialEq)] pub enum FindRange { SingleLine, @@ -14,11 +20,13 @@ pub enum FindRange { /// TextLayoutDetails encompasses everything we need to move vertically /// taking into account variable width characters. pub struct TextLayoutDetails { - pub text_system: Arc, - pub editor_style: EditorStyle, - pub rem_size: Pixels, + pub(crate) text_system: Arc, + pub(crate) editor_style: EditorStyle, + pub(crate) rem_size: Pixels, } +/// Returns a column to the left of the current point, wrapping +/// to the previous line if that point is at the start of line. pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { if point.column() > 0 { *point.column_mut() -= 1; @@ -29,6 +37,8 @@ pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { map.clip_point(point, Bias::Left) } +/// Returns a column to the left of the current point, doing nothing if +/// that point is already at the start of line. pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { if point.column() > 0 { *point.column_mut() -= 1; @@ -36,6 +46,8 @@ pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> Displa map.clip_point(point, Bias::Left) } +/// Returns a column to the right of the current point, wrapping +/// to the next line if that point is at the end of line. pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { let max_column = map.line_len(point.row()); if point.column() < max_column { @@ -47,11 +59,14 @@ pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { map.clip_point(point, Bias::Right) } +/// Returns a column to the right of the current point, not performing any wrapping +/// if that point is already at the end of line. pub fn saturating_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { *point.column_mut() += 1; map.clip_point(point, Bias::Right) } +/// Returns a display point for the preceding displayed line (which might be a soft-wrapped line). pub fn up( map: &DisplaySnapshot, start: DisplayPoint, @@ -69,6 +84,7 @@ pub fn up( ) } +/// Returns a display point for the next displayed line (which might be a soft-wrapped line). pub fn down( map: &DisplaySnapshot, start: DisplayPoint, @@ -86,7 +102,7 @@ pub fn down( ) } -pub fn up_by_rows( +pub(crate) fn up_by_rows( map: &DisplaySnapshot, start: DisplayPoint, row_count: u32, @@ -125,7 +141,7 @@ pub fn up_by_rows( ) } -pub fn down_by_rows( +pub(crate) fn down_by_rows( map: &DisplaySnapshot, start: DisplayPoint, row_count: u32, @@ -161,6 +177,10 @@ pub fn down_by_rows( ) } +/// Returns a position of the start of line. +/// If `stop_at_soft_boundaries` is true, the returned position is that of the +/// displayed line (e.g. it could actually be in the middle of a text line if that line is soft-wrapped). +/// Otherwise it's always going to be the start of a logical line. pub fn line_beginning( map: &DisplaySnapshot, display_point: DisplayPoint, @@ -177,6 +197,10 @@ pub fn line_beginning( } } +/// Returns the last indented position on a given line. +/// If `stop_at_soft_boundaries` is true, the returned [`DisplayPoint`] is that of a +/// displayed line (e.g. if there's soft wrap it's gonna be returned), +/// otherwise it's always going to be a start of a logical line. pub fn indented_line_beginning( map: &DisplaySnapshot, display_point: DisplayPoint, @@ -201,6 +225,11 @@ pub fn indented_line_beginning( } } +/// Returns a position of the end of line. + +/// If `stop_at_soft_boundaries` is true, the returned position is that of the +/// displayed line (e.g. it could actually be in the middle of a text line if that line is soft-wrapped). +/// Otherwise it's always going to be the end of a logical line. pub fn line_end( map: &DisplaySnapshot, display_point: DisplayPoint, @@ -217,6 +246,8 @@ pub fn line_end( } } +/// Returns a position of the previous word boundary, where a word character is defined as either +/// uppercase letter, lowercase letter, '_' character or language-specific word character (like '-' in CSS). pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { let raw_point = point.to_point(map); let scope = map.buffer_snapshot.language_scope_at(raw_point); @@ -227,6 +258,9 @@ pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> Displa }) } +/// Returns a position of the previous subword boundary, where a subword is defined as a run of +/// word characters of the same "subkind" - where subcharacter kinds are '_' character, +/// lowerspace characters and uppercase characters. pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { let raw_point = point.to_point(map); let scope = map.buffer_snapshot.language_scope_at(raw_point); @@ -240,6 +274,8 @@ pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> Dis }) } +/// Returns a position of the next word boundary, where a word character is defined as either +/// uppercase letter, lowercase letter, '_' character or language-specific word character (like '-' in CSS). pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { let raw_point = point.to_point(map); let scope = map.buffer_snapshot.language_scope_at(raw_point); @@ -250,6 +286,9 @@ pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint }) } +/// Returns a position of the next subword boundary, where a subword is defined as a run of +/// word characters of the same "subkind" - where subcharacter kinds are '_' character, +/// lowerspace characters and uppercase characters. pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint { let raw_point = point.to_point(map); let scope = map.buffer_snapshot.language_scope_at(raw_point); @@ -263,6 +302,8 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo }) } +/// Returns a position of the start of the current paragraph, where a paragraph +/// is defined as a run of non-blank lines. pub fn start_of_paragraph( map: &DisplaySnapshot, display_point: DisplayPoint, @@ -290,6 +331,8 @@ pub fn start_of_paragraph( DisplayPoint::zero() } +/// Returns a position of the end of the current paragraph, where a paragraph +/// is defined as a run of non-blank lines. pub fn end_of_paragraph( map: &DisplaySnapshot, display_point: DisplayPoint, @@ -376,6 +419,9 @@ pub fn find_boundary( map.clip_point(offset.to_display_point(map), Bias::Right) } +/// Returns an iterator over the characters following a given offset in the [`DisplaySnapshot`]. +/// The returned value also contains a range of the start/end of a returned character in +/// the [`DisplaySnapshot`]. The offsets are relative to the start of a buffer. pub fn chars_after( map: &DisplaySnapshot, mut offset: usize, @@ -387,6 +433,9 @@ pub fn chars_after( }) } +/// Returns a reverse iterator over the characters following a given offset in the [`DisplaySnapshot`]. +/// The returned value also contains a range of the start/end of a returned character in +/// the [`DisplaySnapshot`]. The offsets are relative to the start of a buffer. pub fn chars_before( map: &DisplaySnapshot, mut offset: usize, @@ -400,7 +449,7 @@ pub fn chars_before( }) } -pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool { +pub(crate) fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool { let raw_point = point.to_point(map); let scope = map.buffer_snapshot.language_scope_at(raw_point); let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left); @@ -413,7 +462,10 @@ pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool { prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word)) } -pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range { +pub(crate) fn surrounding_word( + map: &DisplaySnapshot, + position: DisplayPoint, +) -> Range { let position = map .clip_point(position, Bias::Left) .to_offset(map, Bias::Left); @@ -429,6 +481,12 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range< start..end } +/// Returns a list of lines (represented as a [`DisplayPoint`] range) contained +/// within a passed range. +/// +/// The line ranges are **always* going to be in bounds of a requested range, which means that +/// the first and the last lines might not necessarily represent the +/// full range of a logical line (as their `.start`/`.end` values are clipped to those of a passed in range). pub fn split_display_range_by_lines( map: &DisplaySnapshot, range: Range, diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index bc5fe4bddd1b38d9445f69bd481145a2f3c884f7..f68004109e8889600c08b91ffe1edc5cc54ddee1 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -1,6 +1,6 @@ -pub mod actions; -pub mod autoscroll; -pub mod scroll_amount; +mod actions; +pub(crate) mod autoscroll; +pub(crate) mod scroll_amount; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, @@ -9,8 +9,10 @@ use crate::{ Anchor, DisplayPoint, Editor, EditorEvent, EditorMode, InlayHintRefreshReason, MultiBufferSnapshot, ToPoint, }; +pub use autoscroll::{Autoscroll, AutoscrollStrategy}; use gpui::{point, px, AppContext, Entity, Pixels, Task, ViewContext}; use language::{Bias, Point}; +pub use scroll_amount::ScrollAmount; use std::{ cmp::Ordering, time::{Duration, Instant}, @@ -18,11 +20,6 @@ use std::{ use util::ResultExt; use workspace::{ItemId, WorkspaceId}; -use self::{ - autoscroll::{Autoscroll, AutoscrollStrategy}, - scroll_amount::ScrollAmount, -}; - pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28); pub const VERTICAL_SCROLL_MARGIN: f32 = 3.; const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); diff --git a/crates/editor/src/scroll/autoscroll.rs b/crates/editor/src/scroll/autoscroll.rs index 2a5ac568b79bb9df45489bf5e6b37f37ffcba25b..955b970540ca21d3d23c90740a508d2fe862b6af 100644 --- a/crates/editor/src/scroll/autoscroll.rs +++ b/crates/editor/src/scroll/autoscroll.rs @@ -175,7 +175,7 @@ impl Editor { true } - pub fn autoscroll_horizontally( + pub(crate) fn autoscroll_horizontally( &mut self, start_row: u32, viewport_width: Pixels, diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 8d71916210a6897b4fa649eef2ebbe05a041acb9..96f9507dd2aace6bbd25bbda62a25d68f07c8e3b 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -99,7 +99,7 @@ impl SelectionsCollection { .map(|pending| pending.map(|p| p.summary::(&self.buffer(cx)))) } - pub fn pending_mode(&self) -> Option { + pub(crate) fn pending_mode(&self) -> Option { self.pending.as_ref().map(|pending| pending.mode.clone()) } @@ -398,7 +398,7 @@ impl<'a> MutableSelectionsCollection<'a> { } } - pub fn set_pending_anchor_range(&mut self, range: Range, mode: SelectMode) { + pub(crate) fn set_pending_anchor_range(&mut self, range: Range, mode: SelectMode) { self.collection.pending = Some(PendingSelection { selection: Selection { id: post_inc(&mut self.collection.next_selection_id), @@ -412,7 +412,11 @@ impl<'a> MutableSelectionsCollection<'a> { self.selections_changed = true; } - pub fn set_pending_display_range(&mut self, range: Range, mode: SelectMode) { + pub(crate) fn set_pending_display_range( + &mut self, + range: Range, + mode: SelectMode, + ) { let (start, end, reversed) = { let display_map = self.display_map(); let buffer = self.buffer(); @@ -448,7 +452,7 @@ impl<'a> MutableSelectionsCollection<'a> { self.selections_changed = true; } - pub fn set_pending(&mut self, selection: Selection, mode: SelectMode) { + pub(crate) fn set_pending(&mut self, selection: Selection, mode: SelectMode) { self.collection.pending = Some(PendingSelection { selection, mode }); self.selections_changed = true; } @@ -855,7 +859,7 @@ impl<'a> DerefMut for MutableSelectionsCollection<'a> { } // Panics if passed selections are not in order -pub fn resolve_multiple<'a, D, I>( +pub(crate) fn resolve_multiple<'a, D, I>( selections: I, snapshot: &MultiBufferSnapshot, ) -> impl 'a + Iterator> diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 022860ea4718dbf39717cc86c631721d5e86621b..8484843c87253c6b1aa6b801e46435048d1558a4 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1,5 +1,5 @@ use collections::HashMap; -use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; +use editor::{scroll::Autoscroll, Bias, Editor}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use gpui::{ actions, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index b7e3f27fac257bab08ac1e85401cf7cb5a383dfc..0a74f1ac03f96f5ea121b14da67993f40a2ddfd1 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -1,4 +1,4 @@ -use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor}; +use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Editor}; use gpui::{ actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 1ffab2f3d3e4e6e34514e148b68b4fba48dd2aa5..b15da05e1737d3e21a7c3e84fb2a8e5184330be9 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -1,6 +1,6 @@ use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; -use editor::scroll::autoscroll::Autoscroll; +use editor::scroll::Autoscroll; use editor::Editor; use gpui::{actions, AppContext, ViewContext, WindowContext}; use schemars::JsonSchema; diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 75b4305b58329a48cd19563fc659be6a27f09af0..b4e2b37e83fd52a26c63f97857a93e444323bc0a 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1,5 +1,5 @@ use collections::{HashMap, VecDeque}; -use editor::{Editor, EditorEvent, MoveToEnd}; +use editor::{actions::MoveToEnd, Editor, EditorEvent}; use futures::{channel::mpsc, StreamExt}; use gpui::{ actions, div, AnchorCorner, AnyElement, AppContext, Context, EventEmitter, FocusHandle, diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 5acc6bff7fb1472697bd95c363c2914470457ea3..be677b215b744c239e0a290ad54c7bf23bfca2d4 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -1,4 +1,4 @@ -use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId}; +use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId}; use gpui::{ actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div, EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 1f2112003974aeb95ba55202af87ec20e7e04331..53f78b8fbd45bbf208aa7cc537b4d5d3638ded40 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -1,6 +1,6 @@ use editor::{ - display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt, - DisplayPoint, Editor, EditorMode, ToPoint, + display_map::ToDisplayPoint, scroll::Autoscroll, Anchor, AnchorRangeExt, DisplayPoint, Editor, + EditorMode, ToPoint, }; use fuzzy::StringMatch; use gpui::{ diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4301b6e392df1af4d2f411fc6e51b50564ffb16f..79c158048ee7e08b79f9adcc1d31fa1eea587e44 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -3,7 +3,7 @@ mod project_panel_settings; use settings::Settings; use db::kvp::KEY_VALUE_STORE; -use editor::{scroll::autoscroll::Autoscroll, Cancel, Editor}; +use editor::{actions::Cancel, scroll::Autoscroll, Editor}; use file_associations::FileAssociations; use anyhow::{anyhow, Result}; diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 68a0721b4c54386f31176aef44fb6a0733b02524..3d3e2328954d628d9c61cef7d0fb2c49f27464cf 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -1,4 +1,4 @@ -use editor::{scroll::autoscroll::Autoscroll, styled_runs_for_code_label, Bias, Editor}; +use editor::{scroll::Autoscroll, styled_runs_for_code_label, Bias, Editor}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, rems, AppContext, DismissEvent, FontWeight, Model, ParentElement, StyledText, Task, diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/quick_action_bar/src/quick_action_bar.rs index 865632142a48978f33a446a49fe65c20183cf832..3e49328c133231ef06bad3123467cd25af4fe97a 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/quick_action_bar/src/quick_action_bar.rs @@ -45,13 +45,13 @@ impl Render for QuickActionBar { "toggle inlay hints", IconName::InlayHint, editor.read(cx).inlay_hints_enabled(), - Box::new(editor::ToggleInlayHints), + Box::new(editor::actions::ToggleInlayHints), "Toggle Inlay Hints", { let editor = editor.clone(); move |_, cx| { editor.update(cx, |editor, cx| { - editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx); + editor.toggle_inlay_hints(&editor::actions::ToggleInlayHints, cx); }); } }, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index c4e3ea5b5cb4ff9719221632cd6bf8cdfed8f5fa..a1f0d9773be242818fa31cdb26a5a0de9e532dcd 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -7,7 +7,7 @@ use crate::{ ToggleCaseSensitive, ToggleReplace, ToggleWholeWord, }; use collections::HashMap; -use editor::{Editor, EditorElement, EditorStyle, Tab}; +use editor::{actions::Tab, Editor, EditorElement, EditorStyle}; use futures::channel::oneshot; use gpui::{ actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView, @@ -635,7 +635,7 @@ impl BufferSearchBar { registrar.register_handler(|this, action: &SelectAllMatches, cx| { this.select_all_matches(action, cx); }); - registrar.register_handler(|this, _: &editor::Cancel, cx| { + registrar.register_handler(|this, _: &editor::actions::Cancel, cx| { this.dismiss(&Dismiss, cx); }); registrar.register_handler_for_dismissed_search(|this, deploy, cx| { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 098fa184e353ab58b05a6e5709b0eb62f1a5a16f..919129ef76be9ffb21f77b809b42304f7a910beb 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,8 +7,8 @@ use crate::{ use anyhow::{Context as _, Result}; use collections::HashMap; use editor::{ - items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, EditorEvent, - MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, + actions::SelectAll, items::active_match_index, scroll::Autoscroll, Anchor, Editor, EditorEvent, + MultiBuffer, MAX_TAB_TITLE_LEN, }; use editor::{EditorElement, EditorStyle}; use gpui::{ @@ -1383,11 +1383,11 @@ impl ProjectSearchBar { } } - fn tab(&mut self, _: &editor::Tab, cx: &mut ViewContext) { + fn tab(&mut self, _: &editor::actions::Tab, cx: &mut ViewContext) { self.cycle_field(Direction::Next, cx); } - fn tab_previous(&mut self, _: &editor::TabPrev, cx: &mut ViewContext) { + fn tab_previous(&mut self, _: &editor::actions::TabPrev, cx: &mut ViewContext) { self.cycle_field(Direction::Prev, cx); } diff --git a/crates/storybook/src/stories/auto_height_editor.rs b/crates/storybook/src/stories/auto_height_editor.rs index 6d835155621c44993c701d83151f420b527de794..7b3cee92c8bad783eb8d1b71d2188caaf53cb521 100644 --- a/crates/storybook/src/stories/auto_height_editor.rs +++ b/crates/storybook/src/stories/auto_height_editor.rs @@ -10,7 +10,11 @@ pub struct AutoHeightEditorStory { impl AutoHeightEditorStory { pub fn new(cx: &mut WindowContext) -> View { - cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]); + cx.bind_keys([KeyBinding::new( + "enter", + editor::actions::Newline, + Some("Editor"), + )]); cx.new_view(|cx| Self { editor: cx.new_view(|cx| { let mut editor = Editor::auto_height(3, cx); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index db4b21627f13a3e050888ca1cf18a06488484b1a..16e1ca4a73a45e0d9eb76082894313cdecc8261b 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -2,7 +2,7 @@ mod persistence; pub mod terminal_element; pub mod terminal_panel; -use editor::{scroll::autoscroll::Autoscroll, Editor}; +use editor::{scroll::Autoscroll, Editor}; use gpui::{ div, impl_actions, overlay, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton, MouseDownEvent, Pixels, @@ -357,7 +357,7 @@ impl TerminalView { } } - fn select_all(&mut self, _: &editor::SelectAll, cx: &mut ViewContext) { + fn select_all(&mut self, _: &editor::actions::SelectAll, cx: &mut ViewContext) { self.terminal.update(cx, |term, _| term.select_all()); cx.notify(); } diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 34d8658afe54cd2efdbfcfa1ef5a469070488d75..f1b4853feb75f683c6aafdbeff7ba78cdb615274 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -1,5 +1,5 @@ use command_palette::CommandInterceptResult; -use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive}; +use editor::actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive}; use gpui::{impl_actions, Action, AppContext, ViewContext}; use serde_derive::Deserialize; use workspace::{SaveIntent, Workspace}; @@ -204,25 +204,31 @@ pub fn command_interceptor(mut query: &str, _: &AppContext) -> Option ("clist", diagnostics::Deploy.boxed_clone()), - "cc" => ("cc", editor::Hover.boxed_clone()), - "ll" => ("ll", editor::Hover.boxed_clone()), - "cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()), - "lne" | "lnex" | "lnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()), - - "cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => { - ("cprevious", editor::GoToPrevDiagnostic.boxed_clone()) + "cc" => ("cc", editor::actions::Hover.boxed_clone()), + "ll" => ("ll", editor::actions::Hover.boxed_clone()), + "cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::actions::GoToDiagnostic.boxed_clone()), + "lne" | "lnex" | "lnext" => ("cnext", editor::actions::GoToDiagnostic.boxed_clone()), + + "cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => ( + "cprevious", + editor::actions::GoToPrevDiagnostic.boxed_clone(), + ), + "cN" | "cNe" | "cNex" | "cNext" => { + ("cNext", editor::actions::GoToPrevDiagnostic.boxed_clone()) } - "cN" | "cNe" | "cNex" | "cNext" => ("cNext", editor::GoToPrevDiagnostic.boxed_clone()), - "lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => { - ("lprevious", editor::GoToPrevDiagnostic.boxed_clone()) + "lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => ( + "lprevious", + editor::actions::GoToPrevDiagnostic.boxed_clone(), + ), + "lN" | "lNe" | "lNex" | "lNext" => { + ("lNext", editor::actions::GoToPrevDiagnostic.boxed_clone()) } - "lN" | "lNe" | "lNex" | "lNext" => ("lNext", editor::GoToPrevDiagnostic.boxed_clone()), // modify the buffer (should accept [range]) "j" | "jo" | "joi" | "join" => ("join", JoinLines.boxed_clone()), "d" | "de" | "del" | "dele" | "delet" | "delete" | "dl" | "dell" | "delel" | "deletl" | "deletel" | "dp" | "dep" | "delp" | "delep" | "deletp" | "deletep" => { - ("delete", editor::DeleteLine.boxed_clone()) + ("delete", editor::actions::DeleteLine.boxed_clone()) } "sor" | "sor " | "sort" | "sort " => ("sort", SortLinesCaseSensitive.boxed_clone()), "sor i" | "sort i" => ("sort i", SortLinesCaseInsensitive.boxed_clone()), diff --git a/crates/vim/src/insert.rs b/crates/vim/src/insert.rs index 7c272318287bf323e693121d0e0400cedaa83e75..a063d3747552780806244b08ae03d3476b31d6dc 100644 --- a/crates/vim/src/insert.rs +++ b/crates/vim/src/insert.rs @@ -1,5 +1,5 @@ use crate::{normal::repeat, state::Mode, Vim}; -use editor::{scroll::autoscroll::Autoscroll, Bias}; +use editor::{scroll::Autoscroll, Bias}; use gpui::{actions, Action, ViewContext}; use language::SelectionGoal; use workspace::Workspace; diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 73ba70d5add11f33d55e001e2637c05bcca0afe5..6215e4c16c0d1ebb10eb59358a87d1978bc45c5d 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1,11 +1,10 @@ use editor::{ - char_kind, display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint}, movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails}, - Bias, CharKind, DisplayPoint, ToOffset, + Bias, DisplayPoint, ToOffset, }; use gpui::{actions, impl_actions, px, ViewContext, WindowContext}; -use language::{Point, Selection, SelectionGoal}; +use language::{char_kind, CharKind, Point, Selection, SelectionGoal}; use serde::Deserialize; use workspace::Workspace; diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index a8f2e5fa5a5995949652e8e3c1440f3cb9f90670..c21f54f2d39f2b5a84f65928c1c93505d335d3dc 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -18,7 +18,7 @@ use crate::{ Vim, }; use collections::HashSet; -use editor::scroll::autoscroll::Autoscroll; +use editor::scroll::Autoscroll; use editor::{Bias, DisplayPoint}; use gpui::{actions, ViewContext, WindowContext}; use language::SelectionGoal; diff --git a/crates/vim/src/normal/case.rs b/crates/vim/src/normal/case.rs index 22d09f8359f52060a7435cef321ca0c7f71bd37c..d94454891f7259767605b4335372bf26ba600157 100644 --- a/crates/vim/src/normal/case.rs +++ b/crates/vim/src/normal/case.rs @@ -1,4 +1,4 @@ -use editor::scroll::autoscroll::Autoscroll; +use editor::scroll::Autoscroll; use gpui::ViewContext; use language::{Bias, Point}; use workspace::Workspace; diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index bf2a25a98d5ccf622a7b802b84962d6aed6bf308..86b77038461577909ffe49445fe442cbb0e54f69 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -1,13 +1,12 @@ use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim}; use editor::{ - char_kind, display_map::DisplaySnapshot, movement::{self, FindRange, TextLayoutDetails}, - scroll::autoscroll::Autoscroll, - CharKind, DisplayPoint, + scroll::Autoscroll, + DisplayPoint, }; use gpui::WindowContext; -use language::Selection; +use language::{char_kind, CharKind, Selection}; pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option, cx: &mut WindowContext) { // Some motions ignore failure when switching to normal mode diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index b8105aeb8d7b7b22118179f2194fbcc551090ffb..ed9cdf19fad152a5e753942a8930c02d6666826a 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -1,6 +1,6 @@ use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim}; use collections::{HashMap, HashSet}; -use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias}; +use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Bias}; use gpui::WindowContext; use language::Point; diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index 9fa06c48513817242ef496043b943c214eb3bbad..6353a881ed5d702bdecc6f0d1f5846032f4ea727 100644 --- a/crates/vim/src/normal/increment.rs +++ b/crates/vim/src/normal/increment.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint}; +use editor::{scroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint}; use gpui::{impl_actions, ViewContext, WindowContext}; use language::{Bias, Point}; use serde::Deserialize; diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 169c2c4728f13a8cc8c6d2985079b254c735df23..a65a81665429b1a78f79e9710c22340e106234b0 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -1,8 +1,7 @@ use std::{borrow::Cow, cmp}; use editor::{ - display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection, - DisplayPoint, + display_map::ToDisplayPoint, movement, scroll::Autoscroll, ClipboardSelection, DisplayPoint, }; use gpui::{impl_actions, ViewContext}; use language::{Bias, SelectionGoal}; diff --git a/crates/vim/src/normal/repeat.rs b/crates/vim/src/normal/repeat.rs index a643c126ef4edbd00442578059b9db55cb3e5774..c6d1f0e6c352ceef55926363553d4314ce22f42d 100644 --- a/crates/vim/src/normal/repeat.rs +++ b/crates/vim/src/normal/repeat.rs @@ -12,7 +12,7 @@ actions!(vim, [Repeat, EndRepeat]); fn should_replay(action: &Box) -> bool { // skip so that we don't leave the character palette open - if editor::ShowCharacterPalette.partial_eq(&**action) { + if editor::actions::ShowCharacterPalette.partial_eq(&**action) { return false; } true diff --git a/crates/vim/src/normal/scroll.rs b/crates/vim/src/normal/scroll.rs index 84a27e20cee6984ea04f16935f3e936a7333acf3..8c061582315a907c9a3dbf46b0a8ed0ce9cf3e1c 100644 --- a/crates/vim/src/normal/scroll.rs +++ b/crates/vim/src/normal/scroll.rs @@ -1,7 +1,7 @@ use crate::Vim; use editor::{ display_map::ToDisplayPoint, - scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN}, + scroll::{ScrollAmount, VERTICAL_SCROLL_MARGIN}, DisplayPoint, Editor, }; use gpui::{actions, ViewContext}; diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 47d1647dc765b6c76896cb1bdfc3af0fff5a7f07..1e9361618c5cd6874a3d869dabe9ba01e411599c 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -1,13 +1,12 @@ use std::ops::Range; use editor::{ - char_kind, display_map::{DisplaySnapshot, ToDisplayPoint}, movement::{self, FindRange}, - Bias, CharKind, DisplayPoint, + Bias, DisplayPoint, }; use gpui::{actions, impl_actions, ViewContext, WindowContext}; -use language::Selection; +use language::{char_kind, CharKind, Selection}; use serde::Deserialize; use workspace::Workspace; diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 1fd11167c6843206ddcef2b4ccee96a0f9886dae..797a271574800b72847bd733c32a4764e3ec023c 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -5,7 +5,7 @@ use collections::HashMap; use editor::{ display_map::{DisplaySnapshot, ToDisplayPoint}, movement, - scroll::autoscroll::Autoscroll, + scroll::Autoscroll, Bias, DisplayPoint, Editor, }; use gpui::{actions, ViewContext, WindowContext}; diff --git a/crates/zed/src/app_menus.rs b/crates/zed/src/app_menus.rs index 2aff05d884265aac2538973bf9d7b65ed3d54385..fc063a620f18ace58e8017e53bd08fb11ff5c894 100644 --- a/crates/zed/src/app_menus.rs +++ b/crates/zed/src/app_menus.rs @@ -53,39 +53,46 @@ pub fn app_menus() -> Vec> { Menu { name: "Edit", items: vec![ - MenuItem::os_action("Undo", editor::Undo, OsAction::Undo), - MenuItem::os_action("Redo", editor::Redo, OsAction::Redo), + MenuItem::os_action("Undo", editor::actions::Undo, OsAction::Undo), + MenuItem::os_action("Redo", editor::actions::Redo, OsAction::Redo), MenuItem::separator(), - MenuItem::os_action("Cut", editor::Cut, OsAction::Cut), - MenuItem::os_action("Copy", editor::Copy, OsAction::Copy), - MenuItem::os_action("Paste", editor::Paste, OsAction::Paste), + MenuItem::os_action("Cut", editor::actions::Cut, OsAction::Cut), + MenuItem::os_action("Copy", editor::actions::Copy, OsAction::Copy), + MenuItem::os_action("Paste", editor::actions::Paste, OsAction::Paste), MenuItem::separator(), MenuItem::action("Find", search::buffer_search::Deploy { focus: true }), MenuItem::action("Find In Project", workspace::NewSearch), MenuItem::separator(), - MenuItem::action("Toggle Line Comment", editor::ToggleComments::default()), - MenuItem::action("Emoji & Symbols", editor::ShowCharacterPalette), + MenuItem::action( + "Toggle Line Comment", + editor::actions::ToggleComments::default(), + ), + MenuItem::action("Emoji & Symbols", editor::actions::ShowCharacterPalette), ], }, Menu { name: "Selection", items: vec![ - MenuItem::os_action("Select All", editor::SelectAll, OsAction::SelectAll), - MenuItem::action("Expand Selection", editor::SelectLargerSyntaxNode), - MenuItem::action("Shrink Selection", editor::SelectSmallerSyntaxNode), + MenuItem::os_action( + "Select All", + editor::actions::SelectAll, + OsAction::SelectAll, + ), + MenuItem::action("Expand Selection", editor::actions::SelectLargerSyntaxNode), + MenuItem::action("Shrink Selection", editor::actions::SelectSmallerSyntaxNode), MenuItem::separator(), - MenuItem::action("Add Cursor Above", editor::AddSelectionAbove), - MenuItem::action("Add Cursor Below", editor::AddSelectionBelow), + MenuItem::action("Add Cursor Above", editor::actions::AddSelectionAbove), + MenuItem::action("Add Cursor Below", editor::actions::AddSelectionBelow), MenuItem::action( "Select Next Occurrence", - editor::SelectNext { + editor::actions::SelectNext { replace_newest: false, }, ), MenuItem::separator(), - MenuItem::action("Move Line Up", editor::MoveLineUp), - MenuItem::action("Move Line Down", editor::MoveLineDown), - MenuItem::action("Duplicate Selection", editor::DuplicateLine), + MenuItem::action("Move Line Up", editor::actions::MoveLineUp), + MenuItem::action("Move Line Down", editor::actions::MoveLineDown), + MenuItem::action("Duplicate Selection", editor::actions::DuplicateLine), ], }, Menu { @@ -124,13 +131,13 @@ pub fn app_menus() -> Vec> { MenuItem::action("Go to File", file_finder::Toggle), // MenuItem::action("Go to Symbol in Project", project_symbols::Toggle), MenuItem::action("Go to Symbol in Editor", outline::Toggle), - MenuItem::action("Go to Definition", editor::GoToDefinition), - MenuItem::action("Go to Type Definition", editor::GoToTypeDefinition), - MenuItem::action("Find All References", editor::FindAllReferences), + MenuItem::action("Go to Definition", editor::actions::GoToDefinition), + MenuItem::action("Go to Type Definition", editor::actions::GoToTypeDefinition), + MenuItem::action("Find All References", editor::actions::FindAllReferences), MenuItem::action("Go to Line/Column", go_to_line::Toggle), MenuItem::separator(), - MenuItem::action("Next Problem", editor::GoToDiagnostic), - MenuItem::action("Previous Problem", editor::GoToPrevDiagnostic), + MenuItem::action("Next Problem", editor::actions::GoToDiagnostic), + MenuItem::action("Previous Problem", editor::actions::GoToPrevDiagnostic), ], }, Menu { diff --git a/crates/zed/src/open_listener.rs b/crates/zed/src/open_listener.rs index 6db020a785788d1fe0d05cd2a4d10d937f2b5ac4..f3a10208d0d84e6f231944777c19502e9c84877f 100644 --- a/crates/zed/src/open_listener.rs +++ b/crates/zed/src/open_listener.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cli::{ipc, IpcHandshake}; use cli::{ipc::IpcSender, CliRequest, CliResponse}; -use editor::scroll::autoscroll::Autoscroll; +use editor::scroll::Autoscroll; use editor::Editor; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures::channel::{mpsc, oneshot}; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index afc06ad193659e7c015f2039f2efaa0f95a3297a..112c219d2d728b9a21b36d05554358d47a2b0aa2 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -730,7 +730,7 @@ fn open_bundled_file( mod tests { use super::*; use assets::Assets; - use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor, EditorEvent}; + use editor::{scroll::Autoscroll, DisplayPoint, Editor, EditorEvent}; use gpui::{ actions, Action, AnyWindowHandle, AppContext, AssetSource, Entity, TestAppContext, VisualTestContext, WindowHandle, From 17018faa92d7543610f5efb8f2a6acca046a3727 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 17 Jan 2024 15:51:37 -0800 Subject: [PATCH 287/334] Update typos.toml --- typos.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/typos.toml b/typos.toml index 47eb2a0d9c493d4333d808310c99c2632f55e913..2881b65f4844bdbfb3e992d97afe54adbf2c3e7e 100644 --- a/typos.toml +++ b/typos.toml @@ -7,13 +7,15 @@ extend-exclude = [ "crates/zed/src/languages/glsl/*", # File suffixes aren't typos "assets/icons/file_icons/file_types.json", - # :/ - "crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql", + # Not our typos + "assets/themes/src/vscode/*", # Editor and file finder rely on partial typing and custom in-string syntax "crates/file_finder/src/file_finder.rs", "crates/editor/src/editor_tests.rs", + # :/ + "crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql", ] [default] extend-ignore-re = ["ba"] -check-filename = true \ No newline at end of file +check-filename = true From 078fd35f4fbc22c5de5604ebcf5561283532b9d2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 15:52:54 -0800 Subject: [PATCH 288/334] WIP --- crates/editor/src/completions.rs | 28 +++++++++++++++ crates/editor/src/element.rs | 25 +++++++------ .../src/platform/mac/window_appearance.rs | 35 +++++++++++++++++++ 3 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 crates/editor/src/completions.rs create mode 100644 crates/gpui/src/platform/mac/window_appearance.rs diff --git a/crates/editor/src/completions.rs b/crates/editor/src/completions.rs new file mode 100644 index 0000000000000000000000000000000000000000..b9461364af44e686fc7207d2dd1c800a49156394 --- /dev/null +++ b/crates/editor/src/completions.rs @@ -0,0 +1,28 @@ +use futures::Future; +use gpui::Task; +use smallvec::{smallvec, SmallVec}; +use text::Anchor; + +use crate::Editor; + +struct Completions { + trigger_characters: SmallVec<[char; 1]>, + language: Option, + provider: Box Option>>, +} + +impl Completions { + fn new(f: impl Fn(&mut Editor, &Anchor, &str) -> Option> + 'static) -> Self { + Self { + trigger_characters: smallvec![], + language: None, + provider: Box::new(f), + } + } +} + +impl Editor { + /// Provide completions to the editor when the given character is typed + /// + fn provide_completions(config: Completions) {} +} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b82bd55bcf5e898299ca8a3a0e1d88cf37c72367..518e03c27ee1e3670c7b53df213bf9200dd56998 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2543,18 +2543,21 @@ impl EditorElement { move |event: &MouseUpEvent, phase, cx| { if phase == DispatchPhase::Bubble - && interactive_bounds.visibly_contains(&event.position, cx) { - editor.update(cx, |editor, cx| { - Self::mouse_up( - editor, - event, - &position_map, - text_bounds, - &stacking_order, - cx, - ) - }); + // if interactive_bounds.visibly_contains(&event.position, cx) { + editor.update(cx, |editor, cx| { + Self::mouse_up( + editor, + event, + &position_map, + text_bounds, + &stacking_order, + cx, + ) + }); + // } else { + + // } } } }); diff --git a/crates/gpui/src/platform/mac/window_appearance.rs b/crates/gpui/src/platform/mac/window_appearance.rs new file mode 100644 index 0000000000000000000000000000000000000000..2edc896289ef8056424a0399d38ff937155adad2 --- /dev/null +++ b/crates/gpui/src/platform/mac/window_appearance.rs @@ -0,0 +1,35 @@ +use crate::WindowAppearance; +use cocoa::{ + appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight}, + base::id, + foundation::NSString, +}; +use objc::{msg_send, sel, sel_impl}; +use std::ffi::CStr; + +impl WindowAppearance { + pub unsafe fn from_native(appearance: id) -> Self { + let name: id = msg_send![appearance, name]; + if name == NSAppearanceNameVibrantLight { + Self::VibrantLight + } else if name == NSAppearanceNameVibrantDark { + Self::VibrantDark + } else if name == NSAppearanceNameAqua { + Self::Light + } else if name == NSAppearanceNameDarkAqua { + Self::Dark + } else { + println!( + "unknown appearance: {:?}", + CStr::from_ptr(name.UTF8String()) + ); + Self::Light + } + } +} + +#[link(name = "AppKit", kind = "framework")] +extern "C" { + pub static NSAppearanceNameAqua: id; + pub static NSAppearanceNameDarkAqua: id; +} From ec2b299ecb8fe794385054cf04bd81f43d2e699a Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 18 Jan 2024 00:54:07 +0100 Subject: [PATCH 289/334] settings: Suggest fonts bundled in Zed (#4102) Fixes an issue where Zed Sans is not being suggested as a font. Release Notes: - N/A --- crates/gpui/src/platform/mac/text_system.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 06179e126b6b779a56d25b1bf154eef37162a646..a77741074f13e1515198921b95a3a310e4d3d422 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -87,6 +87,9 @@ impl PlatformTextSystem for MacTextSystem { for descriptor in descriptors.into_iter() { names.insert(descriptor.display_name()); } + if let Ok(fonts_in_memory) = self.0.read().memory_source.all_families() { + names.extend(fonts_in_memory); + } names.into_iter().collect() } From 9a3709dbac494841f7f153d5d6b224c3cbbdb365 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 15:56:15 -0800 Subject: [PATCH 290/334] Revert vscode theme changes --- assets/themes/src/vscode/dracula/dracula.json | 2 +- .../themes/src/vscode/night-owl/night-owl-light.json | 6 +++--- assets/themes/src/vscode/night-owl/night-owl.json | 6 +++--- assets/themes/src/vscode/noctis/azureus.json | 2 +- assets/themes/src/vscode/noctis/bordo.json | 2 +- assets/themes/src/vscode/noctis/hibernus.json | 2 +- assets/themes/src/vscode/noctis/lilac.json | 2 +- assets/themes/src/vscode/noctis/lux.json | 2 +- assets/themes/src/vscode/noctis/minimus.json | 2 +- assets/themes/src/vscode/noctis/noctis.json | 2 +- assets/themes/src/vscode/noctis/obscuro.json | 2 +- assets/themes/src/vscode/noctis/sereno.json | 2 +- assets/themes/src/vscode/noctis/uva.json | 2 +- assets/themes/src/vscode/noctis/viola.json | 2 +- .../src/vscode/palenight/palenight-mild-contrast.json | 10 +++++----- .../src/vscode/palenight/palenight-operator.json | 10 +++++----- assets/themes/src/vscode/palenight/palenight.json | 10 +++++----- 17 files changed, 33 insertions(+), 33 deletions(-) diff --git a/assets/themes/src/vscode/dracula/dracula.json b/assets/themes/src/vscode/dracula/dracula.json index e9a29dec179baf2e518e6e82519adafe01ec879d..6604a094d5a194f74d378d25c38fb47a3f29c539 100644 --- a/assets/themes/src/vscode/dracula/dracula.json +++ b/assets/themes/src/vscode/dracula/dracula.json @@ -1024,7 +1024,7 @@ } }, { - "name": "SCSS attribute selector strings", + "name": "SCSS attibute selector strings", "scope": ["meta.attribute-selector.scss"], "settings": { "foreground": "#F1FA8C" diff --git a/assets/themes/src/vscode/night-owl/night-owl-light.json b/assets/themes/src/vscode/night-owl/night-owl-light.json index 627a55ac62b641b4b112fdf9388f6626fe64a018..81e0fc0092279aec3298ff2f77a81137e0340a68 100644 --- a/assets/themes/src/vscode/night-owl/night-owl-light.json +++ b/assets/themes/src/vscode/night-owl/night-owl-light.json @@ -892,14 +892,14 @@ } }, { - "name": "CoffeeScript Variable Assignment", + "name": "CoffeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#31e1eb" } }, { - "name": "CoffeeScript Parameter Function", + "name": "CoffeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#403f53" @@ -1708,7 +1708,7 @@ "keyword.operator.type", "keyword.operator", "keyword", - "punctuation.definition.string", + "punctuation.definintion.string", "punctuation", "variable.other.readwrite.js", "storage.type", diff --git a/assets/themes/src/vscode/night-owl/night-owl.json b/assets/themes/src/vscode/night-owl/night-owl.json index b16c22fb6afc415b17e6f78873b22ce2844eed5a..6d41b6299b4b911ff5d9952e56255a090c981704 100644 --- a/assets/themes/src/vscode/night-owl/night-owl.json +++ b/assets/themes/src/vscode/night-owl/night-owl.json @@ -926,14 +926,14 @@ } }, { - "name": "CoffeeScript Variable Assignment", + "name": "CoffeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#31e1eb" } }, { - "name": "CoffeeScript Parameter Function", + "name": "CoffeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#d6deeb" @@ -1817,7 +1817,7 @@ "keyword.operator.type", "keyword.operator", "keyword", - "punctuation.definition.string", + "punctuation.definintion.string", "punctuation", "variable.other.readwrite.js", "storage.type", diff --git a/assets/themes/src/vscode/noctis/azureus.json b/assets/themes/src/vscode/noctis/azureus.json index 300113a59d0877077534d362c6f5359ccc3e8c48..d550e74811b63e329f46142f6c5bff6e51584b61 100644 --- a/assets/themes/src/vscode/noctis/azureus.json +++ b/assets/themes/src/vscode/noctis/azureus.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/bordo.json b/assets/themes/src/vscode/noctis/bordo.json index 21c8a13511557dfa4cf09fd608af8b0f684ca7ae..a6c4853c3b078e7373f69ae1084ab7a9d5c47784 100644 --- a/assets/themes/src/vscode/noctis/bordo.json +++ b/assets/themes/src/vscode/noctis/bordo.json @@ -389,7 +389,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/hibernus.json b/assets/themes/src/vscode/noctis/hibernus.json index a2870e39058ad6ac9ecd722b28dde0823dafd926..a20a19289ea539b675cb42b3480eb6ff57e90e53 100644 --- a/assets/themes/src/vscode/noctis/hibernus.json +++ b/assets/themes/src/vscode/noctis/hibernus.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/lilac.json b/assets/themes/src/vscode/noctis/lilac.json index a54b4e3c50de40e6a06b58213e9b19eaed00953f..26e0fe422376496a3a91cc7d191a7057dc986073 100644 --- a/assets/themes/src/vscode/noctis/lilac.json +++ b/assets/themes/src/vscode/noctis/lilac.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/lux.json b/assets/themes/src/vscode/noctis/lux.json index 34dc89460e20e332a3e2eaee82ea2dec518a78c1..1f72b0e59cab91cb2255ee1438ace7b0102dfbcf 100644 --- a/assets/themes/src/vscode/noctis/lux.json +++ b/assets/themes/src/vscode/noctis/lux.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/minimus.json b/assets/themes/src/vscode/noctis/minimus.json index a347af76601a975f60ebf2f6c47a7f7d641f7885..88493d99d5993b6d72ef9a1a81228b8a82fe54c3 100644 --- a/assets/themes/src/vscode/noctis/minimus.json +++ b/assets/themes/src/vscode/noctis/minimus.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/noctis.json b/assets/themes/src/vscode/noctis/noctis.json index 61e90c46a9a37752743052edb6b7decb44e3871a..cc270fe526f10f3f1fea30464390f1d8d5a76c8e 100644 --- a/assets/themes/src/vscode/noctis/noctis.json +++ b/assets/themes/src/vscode/noctis/noctis.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/obscuro.json b/assets/themes/src/vscode/noctis/obscuro.json index 97e6f2d71a63b82bc688374f87e5333a5137d09c..26d1a02de84a4bad2056444302b5e9d83faa8312 100644 --- a/assets/themes/src/vscode/noctis/obscuro.json +++ b/assets/themes/src/vscode/noctis/obscuro.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/sereno.json b/assets/themes/src/vscode/noctis/sereno.json index b81da1edcecf18a3a4d52a58c18af2986c172485..05768aff356e40078973f34650143e9089e1971d 100644 --- a/assets/themes/src/vscode/noctis/sereno.json +++ b/assets/themes/src/vscode/noctis/sereno.json @@ -390,7 +390,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/uva.json b/assets/themes/src/vscode/noctis/uva.json index d4139faaf3a5c14356106d0b0da43501cfc554fd..6ccbff372b8a965d9451279380f07f069b8f8f67 100644 --- a/assets/themes/src/vscode/noctis/uva.json +++ b/assets/themes/src/vscode/noctis/uva.json @@ -389,7 +389,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/noctis/viola.json b/assets/themes/src/vscode/noctis/viola.json index 889d2dfc2a96e7707b2a72150f262fa14a61f548..4d474ad31173c6e8e5888faa44e501cbc0e95aaa 100644 --- a/assets/themes/src/vscode/noctis/viola.json +++ b/assets/themes/src/vscode/noctis/viola.json @@ -389,7 +389,7 @@ "source.reason variable.interpolation", "punctuation.definition.directive", "storage.type.modifier", - "keyword.other.class.fields", + "keyword.other.class.fileds", "source.toml entity.other.attribute-name", "source.css entity.name.tag.custom", "sharing.modifier", diff --git a/assets/themes/src/vscode/palenight/palenight-mild-contrast.json b/assets/themes/src/vscode/palenight/palenight-mild-contrast.json index 598a186692c928e5c7cfaabc1f1c073aea74d303..7533d90ffd5752e5ea160ab2c686a2173aa9e4eb 100644 --- a/assets/themes/src/vscode/palenight/palenight-mild-contrast.json +++ b/assets/themes/src/vscode/palenight/palenight-mild-contrast.json @@ -797,14 +797,14 @@ } }, { - "name": "CoffeeScript Variable Assignment", + "name": "CoffeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#89DDFF" } }, { - "name": "CoffeeScript Parameter Function", + "name": "CoffeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#bfc7d5" @@ -1523,14 +1523,14 @@ } }, { - "name": "handlebars entity attribute names", + "name": "handlebars enitity attribute names", "scope": "entity.other.attribute-name.handlebars", "settings": { "foreground": "#89DDFF" } }, { - "name": "handlebars entity attribute values", + "name": "handlebars enitity attribute values", "scope": "entity.other.attribute-value.handlebars variable.parameter.handlebars", "settings": { "foreground": "#7986E7" @@ -1558,7 +1558,7 @@ "keyword.operator.expression.in", "keyword.operator.type", "punctuation.section.embedded.js", - "punctuation.definition.string", + "punctuation.definintion.string", "punctuation" ], "settings": { diff --git a/assets/themes/src/vscode/palenight/palenight-operator.json b/assets/themes/src/vscode/palenight/palenight-operator.json index 635a2ff7607c455cef191222fc5f13fb8c63024b..450d36cb9ae1233086847429ec795d5ff8e41a9f 100644 --- a/assets/themes/src/vscode/palenight/palenight-operator.json +++ b/assets/themes/src/vscode/palenight/palenight-operator.json @@ -797,14 +797,14 @@ } }, { - "name": "CoffeeScript Variable Assignment", + "name": "CoffeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#89DDFF" } }, { - "name": "CoffeeScript Parameter Function", + "name": "CoffeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#bfc7d5" @@ -1523,14 +1523,14 @@ } }, { - "name": "handlebars entity attribute names", + "name": "handlebars enitity attribute names", "scope": "entity.other.attribute-name.handlebars", "settings": { "foreground": "#89DDFF" } }, { - "name": "handlebars entity attribute values", + "name": "handlebars enitity attribute values", "scope": "entity.other.attribute-value.handlebars variable.parameter.handlebars", "settings": { "foreground": "#7986E7" @@ -1558,7 +1558,7 @@ "keyword.operator.expression.in", "keyword.operator.type", "punctuation.section.embedded.js", - "punctuation.definition.string", + "punctuation.definintion.string", "punctuation" ], "settings": { diff --git a/assets/themes/src/vscode/palenight/palenight.json b/assets/themes/src/vscode/palenight/palenight.json index 5cf68749f42ad9d0b81cfaf06cb8639cb8d3e717..cfbf2f8788c13cc66abfeccf9b0d619416fb642b 100644 --- a/assets/themes/src/vscode/palenight/palenight.json +++ b/assets/themes/src/vscode/palenight/palenight.json @@ -797,14 +797,14 @@ } }, { - "name": "CoffeeScript Variable Assignment", + "name": "CoffeScript Variable Assignment", "scope": "variable.assignment.coffee", "settings": { "foreground": "#89DDFF" } }, { - "name": "CoffeeScript Parameter Function", + "name": "CoffeScript Parameter Function", "scope": "variable.parameter.function.coffee", "settings": { "foreground": "#bfc7d5" @@ -1523,14 +1523,14 @@ } }, { - "name": "handlebars entity attribute names", + "name": "handlebars enitity attribute names", "scope": "entity.other.attribute-name.handlebars", "settings": { "foreground": "#89DDFF" } }, { - "name": "handlebars entity attribute values", + "name": "handlebars enitity attribute values", "scope": "entity.other.attribute-value.handlebars variable.parameter.handlebars", "settings": { "foreground": "#7986E7" @@ -1558,7 +1558,7 @@ "keyword.operator.expression.in", "keyword.operator.type", "punctuation.section.embedded.js", - "punctuation.definition.string", + "punctuation.definintion.string", "punctuation" ], "settings": { From 9521f491603059a2ac4e8efee4aa4dc479e09938 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 17 Jan 2024 19:06:19 -0500 Subject: [PATCH 291/334] Clean up references in doc comments in `lsp` crate (#4109) This PR cleans up a handful of references in doc comments in the `lsp` crate so that `rustdoc` will link and display them correctly. Release Notes: - N/A --- crates/lsp/src/lsp.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 0c1574f5aaff21253c1d1eda695939c801283f2e..9b9aa55ed2d5ef572afb0d912481ecad23474e3b 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -584,7 +584,7 @@ impl LanguageServer { Ok(Arc::new(self)) } - /// Sends a shutdown request to the language server process and prepares the `LanguageServer` to be dropped. + /// Sends a shutdown request to the language server process and prepares the [`LanguageServer`] to be dropped. pub fn shutdown(&self) -> Option>> { if let Some(tasks) = self.io_tasks.lock().take() { let response_handlers = self.response_handlers.clone(); @@ -645,7 +645,7 @@ impl LanguageServer { self.on_custom_request(T::METHOD, f) } - /// Register a handler to inspect all language server process stdio. + /// Registers a handler to inspect all language server process stdio. #[must_use] pub fn on_io(&self, f: F) -> Subscription where @@ -659,17 +659,17 @@ impl LanguageServer { } } - /// Removes a request handler registers via [Self::on_request]. + /// Removes a request handler registers via [`Self::on_request`]. pub fn remove_request_handler(&self) { self.notification_handlers.lock().remove(T::METHOD); } - /// Removes a notification handler registers via [Self::on_notification]. + /// Removes a notification handler registers via [`Self::on_notification`]. pub fn remove_notification_handler(&self) { self.notification_handlers.lock().remove(T::METHOD); } - /// Checks if a notification handler has been registered via [Self::on_notification]. + /// Checks if a notification handler has been registered via [`Self::on_notification`]. pub fn has_notification_handler(&self) -> bool { self.notification_handlers.lock().contains_key(T::METHOD) } @@ -1055,12 +1055,12 @@ impl LanguageServer { #[cfg(any(test, feature = "test-support"))] impl FakeLanguageServer { - /// See [LanguageServer::notify] + /// See [`LanguageServer::notify`]. pub fn notify(&self, params: T::Params) { self.server.notify::(params).ok(); } - /// See [LanguageServer::request] + /// See [`LanguageServer::request`]. pub async fn request(&self, params: T::Params) -> Result where T: request::Request, @@ -1070,7 +1070,7 @@ impl FakeLanguageServer { self.server.request::(params).await } - /// Attempts [try_receive_notification], unwrapping if it has not received the specified type yet. + /// Attempts [`Self::try_receive_notification`], unwrapping if it has not received the specified type yet. pub async fn receive_notification(&mut self) -> T::Params { self.server.executor.start_waiting(); self.try_receive_notification::().await.unwrap() From edb204511c0339de1726038bcad4a8a046bf252e Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 16:24:33 -0800 Subject: [PATCH 292/334] Fix selection bug in editor causing selections to be un-ended --- crates/editor/src/completions.rs | 28 --------------- crates/editor/src/element.rs | 32 ++++++++--------- .../src/platform/mac/window_appearance.rs | 35 ------------------- 3 files changed, 15 insertions(+), 80 deletions(-) delete mode 100644 crates/editor/src/completions.rs delete mode 100644 crates/gpui/src/platform/mac/window_appearance.rs diff --git a/crates/editor/src/completions.rs b/crates/editor/src/completions.rs deleted file mode 100644 index b9461364af44e686fc7207d2dd1c800a49156394..0000000000000000000000000000000000000000 --- a/crates/editor/src/completions.rs +++ /dev/null @@ -1,28 +0,0 @@ -use futures::Future; -use gpui::Task; -use smallvec::{smallvec, SmallVec}; -use text::Anchor; - -use crate::Editor; - -struct Completions { - trigger_characters: SmallVec<[char; 1]>, - language: Option, - provider: Box Option>>, -} - -impl Completions { - fn new(f: impl Fn(&mut Editor, &Anchor, &str) -> Option> + 'static) -> Self { - Self { - trigger_characters: smallvec![], - language: None, - provider: Box::new(f), - } - } -} - -impl Editor { - /// Provide completions to the editor when the given character is typed - /// - fn provide_completions(config: Completions) {} -} diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 518e03c27ee1e3670c7b53df213bf9200dd56998..d7ce842e1d7de4e2a278beaa3ff73ec24bc5405f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -456,6 +456,7 @@ impl EditorElement { event: &MouseUpEvent, position_map: &PositionMap, text_bounds: Bounds, + interactive_bounds: &InteractiveBounds, stacking_order: &StackingOrder, cx: &mut ViewContext, ) { @@ -466,7 +467,8 @@ impl EditorElement { editor.select(SelectPhase::End, cx); } - if !pending_nonempty_selections + if interactive_bounds.visibly_contains(&event.position, cx) + && !pending_nonempty_selections && event.modifiers.command && text_bounds.contains(&event.position) && cx.was_top_layer(&event.position, stacking_order) @@ -2542,22 +2544,18 @@ impl EditorElement { let interactive_bounds = interactive_bounds.clone(); move |event: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Bubble - { - // if interactive_bounds.visibly_contains(&event.position, cx) { - editor.update(cx, |editor, cx| { - Self::mouse_up( - editor, - event, - &position_map, - text_bounds, - &stacking_order, - cx, - ) - }); - // } else { - - // } + if phase == DispatchPhase::Bubble { + editor.update(cx, |editor, cx| { + Self::mouse_up( + editor, + event, + &position_map, + text_bounds, + &interactive_bounds, + &stacking_order, + cx, + ) + }); } } }); diff --git a/crates/gpui/src/platform/mac/window_appearance.rs b/crates/gpui/src/platform/mac/window_appearance.rs deleted file mode 100644 index 2edc896289ef8056424a0399d38ff937155adad2..0000000000000000000000000000000000000000 --- a/crates/gpui/src/platform/mac/window_appearance.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::WindowAppearance; -use cocoa::{ - appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight}, - base::id, - foundation::NSString, -}; -use objc::{msg_send, sel, sel_impl}; -use std::ffi::CStr; - -impl WindowAppearance { - pub unsafe fn from_native(appearance: id) -> Self { - let name: id = msg_send![appearance, name]; - if name == NSAppearanceNameVibrantLight { - Self::VibrantLight - } else if name == NSAppearanceNameVibrantDark { - Self::VibrantDark - } else if name == NSAppearanceNameAqua { - Self::Light - } else if name == NSAppearanceNameDarkAqua { - Self::Dark - } else { - println!( - "unknown appearance: {:?}", - CStr::from_ptr(name.UTF8String()) - ); - Self::Light - } - } -} - -#[link(name = "AppKit", kind = "framework")] -extern "C" { - pub static NSAppearanceNameAqua: id; - pub static NSAppearanceNameDarkAqua: id; -} From 4070eefa4916478bcd2121feb42aad8a36edb9cd Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 16:55:54 -0800 Subject: [PATCH 293/334] Use cargo to install typo check --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b9ca8de8e015de0161a1be1c8fddb1f3fa9d825..bf19e1c53a21afc7891e47080fbd84f3912636af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,8 +38,16 @@ jobs: - name: Set up default .cargo/config.toml run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml + - name: Check spelling + run: | + if ! command -v typos > /dev/null; then + cargo install typos-cli + fi + typos + - name: Run style checks uses: ./.github/actions/check_style + tests: name: Run tests runs-on: From 6ea23d0704cd45b4719977ec1c2679c33be038d3 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 17:03:17 -0800 Subject: [PATCH 294/334] Fix canary --- crates/gpui/src/gpui.rs | 1 - typos.toml | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 1c7e7432889b2a20ba2c3bfaf850581c8c323a5c..6f5e30149d9691b3c364d62ab2e3ce6ec7da1b4c 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -88,7 +88,6 @@ use std::{ }; use taffy::TaffyLayoutEngine; -/// Here's a spelling mistake: visibile pub trait Context { type Result; diff --git a/typos.toml b/typos.toml index 2881b65f4844bdbfb3e992d97afe54adbf2c3e7e..09625dbcf9786621cdc73b22baa151fbaf2edd77 100644 --- a/typos.toml +++ b/typos.toml @@ -1,14 +1,15 @@ [files] ignore-files = true extend-exclude = [ - # Vim makes heavy use of partial typing tables - "crates/vim/*", # glsl isn't recognized by this tool "crates/zed/src/languages/glsl/*", # File suffixes aren't typos "assets/icons/file_icons/file_types.json", # Not our typos "assets/themes/src/vscode/*", + "crates/live_kit_server/*" + # Vim makes heavy use of partial typing tables + "crates/vim/*", # Editor and file finder rely on partial typing and custom in-string syntax "crates/file_finder/src/file_finder.rs", "crates/editor/src/editor_tests.rs", From 58333b96dd6706dcc70e9ea46f3bc6ebc6f2bf67 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 17:04:00 -0800 Subject: [PATCH 295/334] Adjust config --- .github/actions/check_style/action.yml | 3 --- .github/workflows/ci.yml | 2 +- typos.toml | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/actions/check_style/action.yml b/.github/actions/check_style/action.yml index 290496d7e7866490286fafeb31d867b73c20ab80..25020e4e4c5399026c1ab32622903a3779ba86b2 100644 --- a/.github/actions/check_style/action.yml +++ b/.github/actions/check_style/action.yml @@ -21,6 +21,3 @@ runs: run: | export SQUAWK_GITHUB_TOKEN=${{ github.token }} . ./script/squawk - - - name: Run spelling check - uses: crate-ci/typos@master diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf19e1c53a21afc7891e47080fbd84f3912636af..2c660b7a0d3883b0bac1e45bdce56a85fd0e6108 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: - name: Check spelling run: | - if ! command -v typos > /dev/null; then + if ! which typos > /dev/null; then cargo install typos-cli fi typos diff --git a/typos.toml b/typos.toml index 09625dbcf9786621cdc73b22baa151fbaf2edd77..115cc14478cb94feb8320fff5b7b67b555864a6d 100644 --- a/typos.toml +++ b/typos.toml @@ -7,7 +7,7 @@ extend-exclude = [ "assets/icons/file_icons/file_types.json", # Not our typos "assets/themes/src/vscode/*", - "crates/live_kit_server/*" + "crates/live_kit_server/*", # Vim makes heavy use of partial typing tables "crates/vim/*", # Editor and file finder rely on partial typing and custom in-string syntax From ab1bea515c41a7e35d8ffdcca3175bca186a62b3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 15:46:36 -0800 Subject: [PATCH 296/334] Store the impersonator id on access tokens created via ZED_IMPERSONATE * Use the impersonator id to prevent these tokens from counting against the impersonated user when limiting the users' total of access tokens. * When connecting using an access token with an impersonator add the impersonator as a field to the tracing span that wraps the task for that connection. * Disallow impersonating users via the admin API token in production, because when using the admin API token, we aren't able to identify the impersonator. Co-authored-by: Marshall --- .../20221109000000_test_schema.sql | 2 + ...0300_add_impersonator_to_access_tokens.sql | 3 + crates/collab/src/api.rs | 4 +- crates/collab/src/auth.rs | 90 +++++++++++---- crates/collab/src/db/queries/access_tokens.rs | 12 +- crates/collab/src/db/tables/access_token.rs | 1 + crates/collab/src/db/tests/db_tests.rs | 104 ++++++++++++++++-- crates/collab/src/rpc.rs | 20 +++- crates/collab/src/tests/test_server.rs | 1 + 9 files changed, 198 insertions(+), 39 deletions(-) create mode 100644 crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 507cf197f70b9bcce2c74a16b1c6ead569965a5d..a7c9331506732d2c87c72849ea7fea7bd4867726 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -19,9 +19,11 @@ CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id"); CREATE TABLE "access_tokens" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "user_id" INTEGER REFERENCES users (id), + "impersonator_id" INTEGER REFERENCES users (id), "hash" VARCHAR(128) ); CREATE INDEX "index_access_tokens_user_id" ON "access_tokens" ("user_id"); +CREATE INDEX "index_access_tokens_impersonator_id" ON "access_tokens" ("impersonator_id"); CREATE TABLE "contacts" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql b/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql new file mode 100644 index 0000000000000000000000000000000000000000..199706473fd383c84845dcd085a7012bc5fd9919 --- /dev/null +++ b/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql @@ -0,0 +1,3 @@ +ALTER TABLE access_tokens ADD COLUMN impersonator_id integer; + +CREATE INDEX "index_access_tokens_impersonator_id" ON "access_tokens" ("impersonator_id"); diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index a28aeac9ab23dd293fbfbd9a7c851709855408d4..24a8f066b256c61d1a4fc46375a50cd8c92676cd 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -157,9 +157,11 @@ async fn create_access_token( .ok_or_else(|| anyhow!("user not found"))?; let mut user_id = user.id; + let mut impersonator_id = None; if let Some(impersonate) = params.impersonate { if user.admin { if let Some(impersonated_user) = app.db.get_user_by_github_login(&impersonate).await? { + impersonator_id = Some(user_id); user_id = impersonated_user.id; } else { return Err(Error::Http( @@ -175,7 +177,7 @@ async fn create_access_token( } } - let access_token = auth::create_access_token(app.db.as_ref(), user_id).await?; + let access_token = auth::create_access_token(app.db.as_ref(), user_id, impersonator_id).await?; let encrypted_access_token = auth::encrypt_access_token(&access_token, params.public_key.clone())?; diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index df3ded28e4a2f1f86ac1421e91dc6be31f7f8408..a32f35fae896781e6014148bd0ccf8a1e1239c40 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -27,6 +27,9 @@ lazy_static! { .unwrap(); } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Impersonator(pub Option); + /// Validates the authorization header. This has two mechanisms, one for the ADMIN_TOKEN /// and one for the access tokens that we issue. pub async fn validate_header(mut req: Request, next: Next) -> impl IntoResponse { @@ -57,28 +60,50 @@ pub async fn validate_header(mut req: Request, next: Next) -> impl Into })?; let state = req.extensions().get::>().unwrap(); - let credentials_valid = if let Some(admin_token) = access_token.strip_prefix("ADMIN_TOKEN:") { - state.config.api_token == admin_token + + // In development, allow impersonation using the admin API token. + // Don't allow this in production because we can't tell who is doing + // the impersonating. + let validate_result = if let (Some(admin_token), true) = ( + access_token.strip_prefix("ADMIN_TOKEN:"), + state.config.is_development(), + ) { + Ok(VerifyAccessTokenResult { + is_valid: state.config.api_token == admin_token, + impersonator_id: None, + }) } else { - verify_access_token(&access_token, user_id, &state.db) - .await - .unwrap_or(false) + verify_access_token(&access_token, user_id, &state.db).await }; - if credentials_valid { - let user = state - .db - .get_user_by_id(user_id) - .await? - .ok_or_else(|| anyhow!("user {} not found", user_id))?; - req.extensions_mut().insert(user); - Ok::<_, Error>(next.run(req).await) - } else { - Err(Error::Http( - StatusCode::UNAUTHORIZED, - "invalid credentials".to_string(), - )) + if let Ok(validate_result) = validate_result { + if validate_result.is_valid { + let user = state + .db + .get_user_by_id(user_id) + .await? + .ok_or_else(|| anyhow!("user {} not found", user_id))?; + + let impersonator = if let Some(impersonator_id) = validate_result.impersonator_id { + let impersonator = state + .db + .get_user_by_id(impersonator_id) + .await? + .ok_or_else(|| anyhow!("user {} not found", impersonator_id))?; + Some(impersonator) + } else { + None + }; + req.extensions_mut().insert(user); + req.extensions_mut().insert(Impersonator(impersonator)); + return Ok::<_, Error>(next.run(req).await); + } } + + Err(Error::Http( + StatusCode::UNAUTHORIZED, + "invalid credentials".to_string(), + )) } const MAX_ACCESS_TOKENS_TO_STORE: usize = 8; @@ -92,13 +117,22 @@ struct AccessTokenJson { /// Creates a new access token to identify the given user. before returning it, you should /// encrypt it with the user's public key. -pub async fn create_access_token(db: &db::Database, user_id: UserId) -> Result { +pub async fn create_access_token( + db: &db::Database, + user_id: UserId, + impersonator_id: Option, +) -> Result { const VERSION: usize = 1; let access_token = rpc::auth::random_token(); let access_token_hash = hash_access_token(&access_token).context("failed to hash access token")?; let id = db - .create_access_token(user_id, &access_token_hash, MAX_ACCESS_TOKENS_TO_STORE) + .create_access_token( + user_id, + impersonator_id, + &access_token_hash, + MAX_ACCESS_TOKENS_TO_STORE, + ) .await?; Ok(serde_json::to_string(&AccessTokenJson { version: VERSION, @@ -137,8 +171,17 @@ pub fn encrypt_access_token(access_token: &str, public_key: String) -> Result, +} + /// verify access token returns true if the given token is valid for the given user. -pub async fn verify_access_token(token: &str, user_id: UserId, db: &Arc) -> Result { +pub async fn verify_access_token( + token: &str, + user_id: UserId, + db: &Arc, +) -> Result { let token: AccessTokenJson = serde_json::from_str(&token)?; let db_token = db.get_access_token(token.id).await?; @@ -154,5 +197,8 @@ pub async fn verify_access_token(token: &str, user_id: UserId, db: &Arc, access_token_hash: &str, max_access_token_count: usize, ) -> Result { @@ -14,19 +15,28 @@ impl Database { let token = access_token::ActiveModel { user_id: ActiveValue::set(user_id), + impersonator_id: ActiveValue::set(impersonator_id), hash: ActiveValue::set(access_token_hash.into()), ..Default::default() } .insert(&*tx) .await?; + let existing_token_filter = if let Some(impersonator_id) = impersonator_id { + access_token::Column::ImpersonatorId.eq(impersonator_id) + } else { + access_token::Column::UserId + .eq(user_id) + .and(access_token::Column::ImpersonatorId.is_null()) + }; + access_token::Entity::delete_many() .filter( access_token::Column::Id.in_subquery( Query::select() .column(access_token::Column::Id) .from(access_token::Entity) - .and_where(access_token::Column::UserId.eq(user_id)) + .cond_where(existing_token_filter) .order_by(access_token::Column::Id, sea_orm::Order::Desc) .limit(10000) .offset(max_access_token_count as u64) diff --git a/crates/collab/src/db/tables/access_token.rs b/crates/collab/src/db/tables/access_token.rs index da7392b98c444f3d83fd549525f9af4a2eb125d3..81d6f3af6020e55112e7bf8555f9dac53cfa8304 100644 --- a/crates/collab/src/db/tables/access_token.rs +++ b/crates/collab/src/db/tables/access_token.rs @@ -7,6 +7,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: AccessTokenId, pub user_id: UserId, + pub impersonator_id: Option, pub hash: String, } diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 5332f227ef4277ada2fce222bb7097ef0da396b3..98c35aa6467e0fcf7d821da8b018946cbe1902b8 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -146,7 +146,7 @@ test_both_dbs!( ); async fn test_create_access_tokens(db: &Arc) { - let user = db + let user_1 = db .create_user( "u1@example.com", false, @@ -158,14 +158,27 @@ async fn test_create_access_tokens(db: &Arc) { .await .unwrap() .user_id; + let user_2 = db + .create_user( + "u2@example.com", + false, + NewUserParams { + github_login: "u2".into(), + github_user_id: 2, + }, + ) + .await + .unwrap() + .user_id; - let token_1 = db.create_access_token(user, "h1", 2).await.unwrap(); - let token_2 = db.create_access_token(user, "h2", 2).await.unwrap(); + let token_1 = db.create_access_token(user_1, None, "h1", 2).await.unwrap(); + let token_2 = db.create_access_token(user_1, None, "h2", 2).await.unwrap(); assert_eq!( db.get_access_token(token_1).await.unwrap(), access_token::Model { id: token_1, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h1".into(), } ); @@ -173,17 +186,19 @@ async fn test_create_access_tokens(db: &Arc) { db.get_access_token(token_2).await.unwrap(), access_token::Model { id: token_2, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h2".into() } ); - let token_3 = db.create_access_token(user, "h3", 2).await.unwrap(); + let token_3 = db.create_access_token(user_1, None, "h3", 2).await.unwrap(); assert_eq!( db.get_access_token(token_3).await.unwrap(), access_token::Model { id: token_3, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h3".into() } ); @@ -191,18 +206,20 @@ async fn test_create_access_tokens(db: &Arc) { db.get_access_token(token_2).await.unwrap(), access_token::Model { id: token_2, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h2".into() } ); assert!(db.get_access_token(token_1).await.is_err()); - let token_4 = db.create_access_token(user, "h4", 2).await.unwrap(); + let token_4 = db.create_access_token(user_1, None, "h4", 2).await.unwrap(); assert_eq!( db.get_access_token(token_4).await.unwrap(), access_token::Model { id: token_4, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h4".into() } ); @@ -210,12 +227,77 @@ async fn test_create_access_tokens(db: &Arc) { db.get_access_token(token_3).await.unwrap(), access_token::Model { id: token_3, - user_id: user, + user_id: user_1, + impersonator_id: None, hash: "h3".into() } ); assert!(db.get_access_token(token_2).await.is_err()); assert!(db.get_access_token(token_1).await.is_err()); + + // An access token for user 2 impersonating user 1 does not + // count against user 1's access token limit (of 2). + let token_5 = db + .create_access_token(user_1, Some(user_2), "h5", 2) + .await + .unwrap(); + assert_eq!( + db.get_access_token(token_5).await.unwrap(), + access_token::Model { + id: token_5, + user_id: user_1, + impersonator_id: Some(user_2), + hash: "h5".into() + } + ); + assert_eq!( + db.get_access_token(token_3).await.unwrap(), + access_token::Model { + id: token_3, + user_id: user_1, + impersonator_id: None, + hash: "h3".into() + } + ); + + // Only a limited number (2) of access tokens are stored for user 2 + // impersonating other users. + let token_6 = db + .create_access_token(user_1, Some(user_2), "h6", 2) + .await + .unwrap(); + let token_7 = db + .create_access_token(user_1, Some(user_2), "h7", 2) + .await + .unwrap(); + assert_eq!( + db.get_access_token(token_6).await.unwrap(), + access_token::Model { + id: token_6, + user_id: user_1, + impersonator_id: Some(user_2), + hash: "h6".into() + } + ); + assert_eq!( + db.get_access_token(token_7).await.unwrap(), + access_token::Model { + id: token_7, + user_id: user_1, + impersonator_id: Some(user_2), + hash: "h7".into() + } + ); + assert!(db.get_access_token(token_5).await.is_err()); + assert_eq!( + db.get_access_token(token_3).await.unwrap(), + access_token::Model { + id: token_3, + user_id: user_1, + impersonator_id: None, + hash: "h3".into() + } + ); } test_both_dbs!( diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 9406b4938a8f2795a89cd8f10238964710eb61f3..c7bbf7f865096cedbde8e1b5bf2e23338d01a824 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1,7 +1,7 @@ mod connection_pool; use crate::{ - auth, + auth::{self, Impersonator}, db::{ self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreateChannelResult, CreatedChannelMessage, Database, InviteMemberResult, MembershipUpdated, MessageId, @@ -65,7 +65,7 @@ use std::{ use time::OffsetDateTime; use tokio::sync::{watch, Semaphore}; use tower::ServiceBuilder; -use tracing::{info_span, instrument, Instrument}; +use tracing::{field, info_span, instrument, Instrument}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); @@ -561,13 +561,17 @@ impl Server { connection: Connection, address: String, user: User, + impersonator: Option, mut send_connection_id: Option>, executor: Executor, ) -> impl Future> { let this = self.clone(); let user_id = user.id; let login = user.github_login; - let span = info_span!("handle connection", %user_id, %login, %address); + let span = info_span!("handle connection", %user_id, %login, %address, impersonator = field::Empty); + if let Some(impersonator) = impersonator { + span.record("impersonator", &impersonator.github_login); + } let mut teardown = self.teardown.subscribe(); async move { let (connection_id, handle_io, mut incoming_rx) = this @@ -839,6 +843,7 @@ pub async fn handle_websocket_request( ConnectInfo(socket_address): ConnectInfo, Extension(server): Extension>, Extension(user): Extension, + Extension(impersonator): Extension, ws: WebSocketUpgrade, ) -> axum::response::Response { if protocol_version != rpc::PROTOCOL_VERSION { @@ -858,7 +863,14 @@ pub async fn handle_websocket_request( let connection = Connection::new(Box::pin(socket)); async move { server - .handle_connection(connection, socket_address, user, None, Executor::Production) + .handle_connection( + connection, + socket_address, + user, + impersonator.0, + None, + Executor::Production, + ) .await .log_err(); } diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index cda0621cb32385a399fdfdaef51821dd531281b2..ea08d83b6cbe71c4516c1c8bab4d46edce6cf60d 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -213,6 +213,7 @@ impl TestServer { server_conn, client_name, user, + None, Some(connection_id_tx), Executor::Deterministic(cx.background_executor().clone()), )) From 69bff7bb77dca9916c07e7890a5abbabdd2d4dff Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 17:35:37 -0800 Subject: [PATCH 297/334] Exclude squawk rule forbidding regular-sized integers --- script/lib/squawk.toml | 4 ++++ script/squawk | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 script/lib/squawk.toml diff --git a/script/lib/squawk.toml b/script/lib/squawk.toml new file mode 100644 index 0000000000000000000000000000000000000000..1090e382863a8950de3da11b9ca34e08f2014e45 --- /dev/null +++ b/script/lib/squawk.toml @@ -0,0 +1,4 @@ +excluded_rules = [ + "prefer-big-int", + "prefer-bigint-over-int", +] diff --git a/script/squawk b/script/squawk index 0fb3e5a3325e8005fe4f0667debc7626cab5c9bd..68977645d06a5d209ccfba757b6cbe427372e039 100755 --- a/script/squawk +++ b/script/squawk @@ -8,13 +8,12 @@ set -e if [ -z "$GITHUB_BASE_REF" ]; then echo 'Not a pull request, skipping squawk modified migrations linting' - return 0 + exit fi SQUAWK_VERSION=0.26.0 SQUAWK_BIN="./target/squawk-$SQUAWK_VERSION" -SQUAWK_ARGS="--assume-in-transaction" - +SQUAWK_ARGS="--assume-in-transaction --config script/lib/squawk.toml" if [ ! -f "$SQUAWK_BIN" ]; then curl -L -o "$SQUAWK_BIN" "https://github.com/sbdchd/squawk/releases/download/v$SQUAWK_VERSION/squawk-darwin-x86_64" From 9f04fd9019018849655cfe4a1cdb983094a316f6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 17:58:59 -0800 Subject: [PATCH 298/334] For impersonating access tokens, store impersonatee in the new column This way, we don't need an index on both columns --- .../20221109000000_test_schema.sql | 3 +- ...0300_add_impersonator_to_access_tokens.sql | 4 +-- crates/collab/src/api.rs | 11 +++--- crates/collab/src/auth.rs | 13 ++++--- crates/collab/src/db/queries/access_tokens.rs | 14 ++------ crates/collab/src/db/tables/access_token.rs | 2 +- crates/collab/src/db/tests/db_tests.rs | 34 +++++++++---------- docs/src/developing_zed__building_zed.md | 2 +- 8 files changed, 38 insertions(+), 45 deletions(-) diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index a7c9331506732d2c87c72849ea7fea7bd4867726..8d8f523c94c2caa2e70212f3e08ae6f4f493599a 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -19,11 +19,10 @@ CREATE INDEX "index_users_on_github_user_id" ON "users" ("github_user_id"); CREATE TABLE "access_tokens" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "user_id" INTEGER REFERENCES users (id), - "impersonator_id" INTEGER REFERENCES users (id), + "impersonated_user_id" INTEGER REFERENCES users (id), "hash" VARCHAR(128) ); CREATE INDEX "index_access_tokens_user_id" ON "access_tokens" ("user_id"); -CREATE INDEX "index_access_tokens_impersonator_id" ON "access_tokens" ("impersonator_id"); CREATE TABLE "contacts" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql b/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql index 199706473fd383c84845dcd085a7012bc5fd9919..8c79640cd88bfad58e5f9eafda90ae2d80e4e834 100644 --- a/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql +++ b/crates/collab/migrations/20240117150300_add_impersonator_to_access_tokens.sql @@ -1,3 +1 @@ -ALTER TABLE access_tokens ADD COLUMN impersonator_id integer; - -CREATE INDEX "index_access_tokens_impersonator_id" ON "access_tokens" ("impersonator_id"); +ALTER TABLE access_tokens ADD COLUMN impersonated_user_id integer; diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 24a8f066b256c61d1a4fc46375a50cd8c92676cd..6bdbd7357fb857c4db90bfc0f5583023d3b76daf 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -156,13 +156,11 @@ async fn create_access_token( .await? .ok_or_else(|| anyhow!("user not found"))?; - let mut user_id = user.id; - let mut impersonator_id = None; + let mut impersonated_user_id = None; if let Some(impersonate) = params.impersonate { if user.admin { if let Some(impersonated_user) = app.db.get_user_by_github_login(&impersonate).await? { - impersonator_id = Some(user_id); - user_id = impersonated_user.id; + impersonated_user_id = Some(impersonated_user.id); } else { return Err(Error::Http( StatusCode::UNPROCESSABLE_ENTITY, @@ -177,12 +175,13 @@ async fn create_access_token( } } - let access_token = auth::create_access_token(app.db.as_ref(), user_id, impersonator_id).await?; + let access_token = + auth::create_access_token(app.db.as_ref(), user_id, impersonated_user_id).await?; let encrypted_access_token = auth::encrypt_access_token(&access_token, params.public_key.clone())?; Ok(Json(CreateAccessTokenResponse { - user_id, + user_id: impersonated_user_id.unwrap_or(user_id), encrypted_access_token, })) } diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index a32f35fae896781e6014148bd0ccf8a1e1239c40..e6c43df73c6dc9972849acc5ef5fff704553f77a 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -120,7 +120,7 @@ struct AccessTokenJson { pub async fn create_access_token( db: &db::Database, user_id: UserId, - impersonator_id: Option, + impersonated_user_id: Option, ) -> Result { const VERSION: usize = 1; let access_token = rpc::auth::random_token(); @@ -129,7 +129,7 @@ pub async fn create_access_token( let id = db .create_access_token( user_id, - impersonator_id, + impersonated_user_id, &access_token_hash, MAX_ACCESS_TOKENS_TO_STORE, ) @@ -185,7 +185,8 @@ pub async fn verify_access_token( let token: AccessTokenJson = serde_json::from_str(&token)?; let db_token = db.get_access_token(token.id).await?; - if db_token.user_id != user_id { + let token_user_id = db_token.impersonated_user_id.unwrap_or(db_token.user_id); + if token_user_id != user_id { return Err(anyhow!("no such access token"))?; } @@ -199,6 +200,10 @@ pub async fn verify_access_token( METRIC_ACCESS_TOKEN_HASHING_TIME.observe(duration.as_millis() as f64); Ok(VerifyAccessTokenResult { is_valid, - impersonator_id: db_token.impersonator_id, + impersonator_id: if db_token.impersonated_user_id.is_some() { + Some(db_token.user_id) + } else { + None + }, }) } diff --git a/crates/collab/src/db/queries/access_tokens.rs b/crates/collab/src/db/queries/access_tokens.rs index e0db6c5038dad69230a0a57fc5401b5117937312..af58d51a3343fd84acb604b01f5030260b558376 100644 --- a/crates/collab/src/db/queries/access_tokens.rs +++ b/crates/collab/src/db/queries/access_tokens.rs @@ -6,7 +6,7 @@ impl Database { pub async fn create_access_token( &self, user_id: UserId, - impersonator_id: Option, + impersonated_user_id: Option, access_token_hash: &str, max_access_token_count: usize, ) -> Result { @@ -15,28 +15,20 @@ impl Database { let token = access_token::ActiveModel { user_id: ActiveValue::set(user_id), - impersonator_id: ActiveValue::set(impersonator_id), + impersonated_user_id: ActiveValue::set(impersonated_user_id), hash: ActiveValue::set(access_token_hash.into()), ..Default::default() } .insert(&*tx) .await?; - let existing_token_filter = if let Some(impersonator_id) = impersonator_id { - access_token::Column::ImpersonatorId.eq(impersonator_id) - } else { - access_token::Column::UserId - .eq(user_id) - .and(access_token::Column::ImpersonatorId.is_null()) - }; - access_token::Entity::delete_many() .filter( access_token::Column::Id.in_subquery( Query::select() .column(access_token::Column::Id) .from(access_token::Entity) - .cond_where(existing_token_filter) + .and_where(access_token::Column::UserId.eq(user_id)) .order_by(access_token::Column::Id, sea_orm::Order::Desc) .limit(10000) .offset(max_access_token_count as u64) diff --git a/crates/collab/src/db/tables/access_token.rs b/crates/collab/src/db/tables/access_token.rs index 81d6f3af6020e55112e7bf8555f9dac53cfa8304..22635fb64d94538687eac590efaf75049c64c864 100644 --- a/crates/collab/src/db/tables/access_token.rs +++ b/crates/collab/src/db/tables/access_token.rs @@ -7,7 +7,7 @@ pub struct Model { #[sea_orm(primary_key)] pub id: AccessTokenId, pub user_id: UserId, - pub impersonator_id: Option, + pub impersonated_user_id: Option, pub hash: String, } diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 98c35aa6467e0fcf7d821da8b018946cbe1902b8..3e1bdede71e3691dc1da0e0df0fb59c6c92ac83b 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -178,7 +178,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_1, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h1".into(), } ); @@ -187,7 +187,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_2, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h2".into() } ); @@ -198,7 +198,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_3, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h3".into() } ); @@ -207,7 +207,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_2, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h2".into() } ); @@ -219,7 +219,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_4, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h4".into() } ); @@ -228,7 +228,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_3, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h3".into() } ); @@ -238,15 +238,15 @@ async fn test_create_access_tokens(db: &Arc) { // An access token for user 2 impersonating user 1 does not // count against user 1's access token limit (of 2). let token_5 = db - .create_access_token(user_1, Some(user_2), "h5", 2) + .create_access_token(user_2, Some(user_1), "h5", 2) .await .unwrap(); assert_eq!( db.get_access_token(token_5).await.unwrap(), access_token::Model { id: token_5, - user_id: user_1, - impersonator_id: Some(user_2), + user_id: user_2, + impersonated_user_id: Some(user_1), hash: "h5".into() } ); @@ -255,7 +255,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_3, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h3".into() } ); @@ -263,19 +263,19 @@ async fn test_create_access_tokens(db: &Arc) { // Only a limited number (2) of access tokens are stored for user 2 // impersonating other users. let token_6 = db - .create_access_token(user_1, Some(user_2), "h6", 2) + .create_access_token(user_2, Some(user_1), "h6", 2) .await .unwrap(); let token_7 = db - .create_access_token(user_1, Some(user_2), "h7", 2) + .create_access_token(user_2, Some(user_1), "h7", 2) .await .unwrap(); assert_eq!( db.get_access_token(token_6).await.unwrap(), access_token::Model { id: token_6, - user_id: user_1, - impersonator_id: Some(user_2), + user_id: user_2, + impersonated_user_id: Some(user_1), hash: "h6".into() } ); @@ -283,8 +283,8 @@ async fn test_create_access_tokens(db: &Arc) { db.get_access_token(token_7).await.unwrap(), access_token::Model { id: token_7, - user_id: user_1, - impersonator_id: Some(user_2), + user_id: user_2, + impersonated_user_id: Some(user_1), hash: "h7".into() } ); @@ -294,7 +294,7 @@ async fn test_create_access_tokens(db: &Arc) { access_token::Model { id: token_3, user_id: user_1, - impersonator_id: None, + impersonated_user_id: None, hash: "h3".into() } ); diff --git a/docs/src/developing_zed__building_zed.md b/docs/src/developing_zed__building_zed.md index 7535ceb4d0193e01b46e1cf1f9e2c818c086f138..a360be83975995a37870900af6bf7166a666c9b6 100644 --- a/docs/src/developing_zed__building_zed.md +++ b/docs/src/developing_zed__building_zed.md @@ -14,7 +14,7 @@ - Ensure that the Xcode command line tools are using your newly installed copy of Xcode: ``` - sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer. + sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer ``` * Install the Rust wasm toolchain: From 93d068a7467cc12ad68d4ab1ca5c0bfd9397aa33 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Jan 2024 18:01:38 -0800 Subject: [PATCH 299/334] Update verify_access_token doc comment --- crates/collab/src/auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/auth.rs b/crates/collab/src/auth.rs index e6c43df73c6dc9972849acc5ef5fff704553f77a..dc0374df6a764d06282b68cdd4042afcd5875ac8 100644 --- a/crates/collab/src/auth.rs +++ b/crates/collab/src/auth.rs @@ -176,7 +176,7 @@ pub struct VerifyAccessTokenResult { pub impersonator_id: Option, } -/// verify access token returns true if the given token is valid for the given user. +/// Checks that the given access token is valid for the given user. pub async fn verify_access_token( token: &str, user_id: UserId, From 680929081d252bb3c41c535704d8b096d4b5de34 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 17 Jan 2024 19:41:02 -0700 Subject: [PATCH 300/334] Send crash reports to Slack automatically --- Cargo.lock | 11 ++ Procfile | 1 - crates/client/src/client.rs | 10 ++ crates/collab/.env.toml | 3 + crates/collab/Cargo.toml | 1 + crates/collab/k8s/collab.template.yml | 15 ++ crates/collab/src/api.rs | 127 ++++++++++++++- crates/collab/src/lib.rs | 3 + crates/collab/src/tests/test_server.rs | 3 + crates/util/src/paths.rs | 2 + crates/zed/src/main.rs | 212 +++++++++++++++++-------- 11 files changed, 319 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c3a4e8592e86cc3ce2c795baa9b1b62fd8b2045..ba85eb14864a9a1af4c3015bdc3154cea01d3a82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1474,6 +1474,7 @@ dependencies = [ "env_logger", "envy", "file_finder", + "form-data-builder", "fs", "futures 0.3.28", "git", @@ -2690,6 +2691,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form-data-builder" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ff8fb4527b05539a9f573ba2831a1127038a7b45eea385a338a63dc5ab6829" +dependencies = [ + "base64 0.13.1", + "rand 0.8.5", +] + [[package]] name = "form_urlencoded" version = "1.2.0" diff --git a/Procfile b/Procfile index 3f42c3a9677477bd7dcd04ca61a749206559be40..122ad5139fe1adb3cee4188f1abd0d0bb41f8042 100644 --- a/Procfile +++ b/Procfile @@ -1,4 +1,3 @@ -web: cd ../zed.dev && PORT=3000 npm run dev collab: cd crates/collab && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve livekit: livekit-server --dev postgrest: postgrest crates/collab/admin_api.conf diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 189402308487870bee29da52c9333283ceb7fd1f..da695f6dc402a99880c87482cc4914e066b38d88 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -969,6 +969,16 @@ impl Client { Url::parse(&collab_url).context("invalid rpc url") } + // todo: this should probably be cached (And/or done better) + pub async fn get_collab_server_url( + http: Arc, + release_channel: Option, + ) -> Result { + let mut url = Self::get_rpc_url(http, release_channel).await?; + url.set_path(""); + Ok(url) + } + fn establish_websocket_connection( self: &Arc, credentials: &Credentials, diff --git a/crates/collab/.env.toml b/crates/collab/.env.toml index 01866012ea8a14c94835d0662318d3f8c4df67f5..46140ae9b32e3c87afbd3db7cc15ff2a8aa86feb 100644 --- a/crates/collab/.env.toml +++ b/crates/collab/.env.toml @@ -2,11 +2,14 @@ DATABASE_URL = "postgres://postgres@localhost/zed" DATABASE_MAX_CONNECTIONS = 5 HTTP_PORT = 8080 API_TOKEN = "secret" +CLIENT_TOKEN = "618033988749894" INVITE_LINK_PREFIX = "http://localhost:3000/invites/" ZED_ENVIRONMENT = "development" LIVE_KIT_SERVER = "http://localhost:7880" LIVE_KIT_KEY = "devkey" LIVE_KIT_SECRET = "secret" +# SLACK_PANIC_CHANNEL = +# SLACK_API_KEY = # RUST_LOG=info # LOG_JSON=true diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index bc273cb12a9b893ac0e0b7d0b710416ea4ba37b8..f703c88bc04c4a9033e2ef522781cebecdd88d3f 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -58,6 +58,7 @@ tracing = "0.1.34" tracing-log = "0.1.3" tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } uuid.workspace = true +form-data-builder = "1.0.1" [dev-dependencies] audio = { path = "../audio" } diff --git a/crates/collab/k8s/collab.template.yml b/crates/collab/k8s/collab.template.yml index 120e5f592f62bfebd1a8c4f6b95d8c665f5c5577..eb9f4d9172bd66497f2299dc0a987c5d6fabba38 100644 --- a/crates/collab/k8s/collab.template.yml +++ b/crates/collab/k8s/collab.template.yml @@ -90,6 +90,11 @@ spec: secretKeyRef: name: api key: token + - name: CLIENT_TOKEN + valueFrom: + secretKeyRef: + name: api + key: client_token - name: LIVE_KIT_SERVER valueFrom: secretKeyRef: @@ -105,6 +110,16 @@ spec: secretKeyRef: name: livekit key: secret + - name: SLACK_PANIC_CHANNEL + valueFrom: + secretKeyRef: + name: slack + key: api_key + - name: SLACK_API_KEY + valueFrom: + secretKeyRef: + name: slack + key: panic_channel - name: INVITE_LINK_PREFIX value: ${INVITE_LINK_PREFIX} - name: RUST_BACKTRACE diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index a28aeac9ab23dd293fbfbd9a7c851709855408d4..daca13fa85935cde15a0b95649a2cb77367f1de3 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -18,19 +18,28 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tower::ServiceBuilder; use tracing::instrument; +use util::{async_maybe, http::AsyncBody, ResultExt}; pub fn routes(rpc_server: Arc, state: Arc) -> Router { - Router::new() + let called_from_website = Router::new() .route("/user", get(get_authenticated_user)) .route("/users/:id/access_tokens", post(create_access_token)) .route("/panic", post(trace_panic)) .route("/rpc_server_snapshot", get(get_rpc_server_snapshot)) .layer( ServiceBuilder::new() - .layer(Extension(state)) + .layer(Extension(state.clone())) .layer(Extension(rpc_server)) .layer(middleware::from_fn(validate_api_token)), - ) + ); + + let called_from_client = Router::new().route("/crash", post(trace_crash)).layer( + ServiceBuilder::new() + .layer(Extension(state)) + .layer(middleware::from_fn(validate_client_secret)), + ); + + called_from_website.merge(called_from_client) } pub async fn validate_api_token(req: Request, next: Next) -> impl IntoResponse { @@ -64,6 +73,37 @@ pub async fn validate_api_token(req: Request, next: Next) -> impl IntoR Ok::<_, Error>(next.run(req).await) } +pub async fn validate_client_secret(req: Request, next: Next) -> impl IntoResponse { + let token = req + .headers() + .get(http::header::AUTHORIZATION) + .and_then(|header| header.to_str().ok()) + .ok_or_else(|| { + Error::Http( + StatusCode::BAD_REQUEST, + "missing authorization header".to_string(), + ) + })? + .strip_prefix("token ") + .ok_or_else(|| { + Error::Http( + StatusCode::BAD_REQUEST, + "invalid authorization header".to_string(), + ) + })?; + + let state = req.extensions().get::>().unwrap(); + + if token != state.config.client_token { + Err(Error::Http( + StatusCode::UNAUTHORIZED, + "invalid client secret".to_string(), + ))? + } + + Ok::<_, Error>(next.run(req).await) +} + #[derive(Debug, Deserialize)] struct AuthenticatedUserParams { github_user_id: Option, @@ -127,6 +167,87 @@ async fn trace_panic(panic: Json) -> Result<()> { Ok(()) } +/// IPSHeader is the first line of an .ips file (in JSON format) +/// https://developer.apple.com/documentation/xcode/interpreting-the-json-format-of-a-crash-report +#[derive(Debug, Serialize, Deserialize)] +struct IPSHeader { + timestamp: Option, + name: Option, + app_name: Option, + app_version: Option, + slice_uuid: Option, + build_version: Option, + platform: Option, + #[serde(rename = "bundleID")] + bundle_id: Option, + share_with_app_devs: Option, + is_first_party: Option, + bug_type: Option, + os_version: Option, + roots_installed: Option, + incident_id: Option, +} + +#[instrument(skip(content, app))] +async fn trace_crash(content: String, Extension(app): Extension>) -> Result<()> { + let Some(header) = content.split("\n").next() else { + return Err(Error::Http( + StatusCode::BAD_REQUEST, + "invalid .ips file".to_string(), + )); + }; + let header: IPSHeader = serde_json::from_slice(&header.as_bytes())?; + let text = content.as_str(); + + tracing::error!(app_version = %header.app_version.clone().unwrap_or_default(), + build_version = %header.build_version.unwrap_or_default(), + os_version = %header.os_version.unwrap_or_default(), + bundle_id = %header.bundle_id.clone().unwrap_or_default(), + text = %text, + "crash report"); + + async_maybe!({ + let api_key = app.config.slack_api_key.clone()?; + let channel = app.config.slack_panic_channel.clone()?; + + let mut body = form_data_builder::FormData::new(Vec::new()); + body.write_field("content", text).log_err()?; + body.write_field("channels", channel.as_str()).log_err()?; + body.write_field( + "filename", + format!("zed-crash-{}.ips", header.incident_id.unwrap_or_default()).as_str(), + ) + .log_err()?; + body.write_field( + "initial_comment", + format!( + "New crash in {} ({})", + header.bundle_id.unwrap_or_default(), + header.app_version.unwrap_or_default() + ) + .as_str(), + ) + .log_err()?; + let content_type = body.content_type_header(); + let body = AsyncBody::from(body.finish().log_err()?); + + let request = Request::post("https://slack.com/api/files.upload") + .header("Content-Type", content_type) + .header("Authorization", format!("Bearer {}", api_key)) + .body(body) + .log_err()?; + + let response = util::http::client().send(request).await.log_err()?; + if !response.status().is_success() { + tracing::error!(response = ?response, "failed to send crash report to slack"); + } + + Some(()) + }) + .await; + Ok(()) +} + async fn get_rpc_server_snapshot( Extension(rpc_server): Extension>, ) -> Result { diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index aba9bd75d1f0aa9cc1849309dcb8f8db5b2ed9e3..bf4270512e6ab4a6247edc000a56c59308a54837 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -101,6 +101,9 @@ pub struct Config { pub rust_log: Option, pub log_json: Option, pub zed_environment: Arc, + pub slack_api_key: Option, + pub slack_panic_channel: Option, + pub client_token: String, } impl Config { diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index cda0621cb32385a399fdfdaef51821dd531281b2..d6ea536dd1a7fc72191028ab62548229c3a95ed0 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -465,6 +465,7 @@ impl TestServer { database_url: "".into(), database_max_connections: 0, api_token: "".into(), + client_token: "".into(), invite_link_prefix: "".into(), live_kit_server: None, live_kit_key: None, @@ -472,6 +473,8 @@ impl TestServer { rust_log: None, log_json: None, zed_environment: "test".into(), + slack_api_key: None, + slack_panic_channel: None, }, }) } diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index ff1f6080f7669ed3ba221e064d6652c7b7f968ec..2df28def4c8979730ee0b53e7a1b3368f48d62bd 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -15,6 +15,8 @@ lazy_static::lazy_static! { pub static ref COPILOT_DIR: PathBuf = HOME.join("Library/Application Support/Zed/copilot"); pub static ref DEFAULT_PRETTIER_DIR: PathBuf = HOME.join("Library/Application Support/Zed/prettier"); pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db"); + pub static ref CRASHES_DIR: PathBuf = HOME.join("Library/Logs/DiagnosticReports"); + pub static ref CRASHES_RETIRED_DIR: PathBuf = HOME.join("Library/Logs/DiagnosticReports/Retired"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt"); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 821668001c4fa42f757eea55b5e80361e0456e6d..ce39549016438b82b3fd9150e10c8891caa6d0e4 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -43,7 +43,8 @@ use util::{ async_maybe, channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL}, http::{self, HttpClient}, - paths, ResultExt, + paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR}, + ResultExt, }; use uuid::Uuid; use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN}; @@ -227,14 +228,14 @@ fn main() { initialize_workspace(app_state.clone(), cx); if stdout_is_a_pty() { + upload_panics_and_crashes(http.clone(), cx); cx.activate(true); let urls = collect_url_args(); if !urls.is_empty() { listener.open_urls(&urls) } } else { - upload_previous_panics(http.clone(), cx); - + upload_panics_and_crashes(http.clone(), cx); // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead // of an *app, hence gets no specific callbacks run. Emulate them here, if needed. if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() @@ -597,77 +598,158 @@ fn init_panic_hook(app: &App, installation_id: Option, session_id: Strin })); } -fn upload_previous_panics(http: Arc, cx: &mut AppContext) { +fn upload_panics_and_crashes(http: Arc, cx: &mut AppContext) { let telemetry_settings = *client::TelemetrySettings::get_global(cx); - + let release_channel = cx.global::().clone(); cx.background_executor() .spawn(async move { - let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); - let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; - while let Some(child) = children.next().await { - let child = child?; - let child_path = child.path(); - - if child_path.extension() != Some(OsStr::new("panic")) { - continue; - } - let filename = if let Some(filename) = child_path.file_name() { - filename.to_string_lossy() - } else { - continue; - }; + upload_previous_panics(http.clone(), telemetry_settings) + .await + .log_err(); + upload_previous_crashes(http, telemetry_settings, release_channel) + .await + .log_err() + }) + .detach() +} - if !filename.starts_with("zed") { - continue; - } +/// upload panics to us (via zed.dev) +async fn upload_previous_panics( + http: Arc, + telemetry_settings: client::TelemetrySettings, +) -> Result<()> { + let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL); + let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?; + while let Some(child) = children.next().await { + let child = child?; + let child_path = child.path(); + + if child_path.extension() != Some(OsStr::new("panic")) { + continue; + } + let filename = if let Some(filename) = child_path.file_name() { + filename.to_string_lossy() + } else { + continue; + }; - if telemetry_settings.diagnostics { - let panic_file_content = smol::fs::read_to_string(&child_path) - .await - .context("error reading panic file")?; - - let panic = serde_json::from_str(&panic_file_content) - .ok() - .or_else(|| { - panic_file_content - .lines() - .next() - .and_then(|line| serde_json::from_str(line).ok()) - }) - .unwrap_or_else(|| { - log::error!( - "failed to deserialize panic file {:?}", - panic_file_content - ); - None - }); + if !filename.starts_with("zed") { + continue; + } - if let Some(panic) = panic { - let body = serde_json::to_string(&PanicRequest { - panic, - token: client::ZED_SECRET_CLIENT_TOKEN.into(), - }) - .unwrap(); - - let request = Request::post(&panic_report_url) - .redirect_policy(isahc::config::RedirectPolicy::Follow) - .header("Content-Type", "application/json") - .body(body.into())?; - let response = http.send(request).await.context("error sending panic")?; - if !response.status().is_success() { - log::error!("Error uploading panic to server: {}", response.status()); - } - } + if telemetry_settings.diagnostics { + let panic_file_content = smol::fs::read_to_string(&child_path) + .await + .context("error reading panic file")?; + + let panic = serde_json::from_str(&panic_file_content) + .ok() + .or_else(|| { + panic_file_content + .lines() + .next() + .and_then(|line| serde_json::from_str(line).ok()) + }) + .unwrap_or_else(|| { + log::error!("failed to deserialize panic file {:?}", panic_file_content); + None + }); + + if let Some(panic) = panic { + let body = serde_json::to_string(&PanicRequest { + panic, + token: client::ZED_SECRET_CLIENT_TOKEN.into(), + }) + .unwrap(); + + let request = Request::post(&panic_report_url) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header("Content-Type", "application/json") + .body(body.into())?; + let response = http.send(request).await.context("error sending panic")?; + if !response.status().is_success() { + log::error!("Error uploading panic to server: {}", response.status()); } + } + } - // We've done what we can, delete the file - std::fs::remove_file(child_path) - .context("error removing panic") - .log_err(); + // We've done what we can, delete the file + std::fs::remove_file(child_path) + .context("error removing panic") + .log_err(); + } + Ok::<_, anyhow::Error>(()) +} + +static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED"; + +/// upload crashes from apple's diagnostic reports to our server. +/// (only if telemetry is enabled) +async fn upload_previous_crashes( + http: Arc, + telemetry_settings: client::TelemetrySettings, + release_channel: ReleaseChannel, +) -> Result<()> { + if !telemetry_settings.diagnostics { + return Ok(()); + } + let last_uploaded = KEY_VALUE_STORE + .read_kvp(LAST_CRASH_UPLOADED)? + .unwrap_or("zed-2024-01-17-000000.ips".to_string()); // don't upload old crash reports from before we had this. + let mut uploaded = last_uploaded.clone(); + + let mut crash_report_url = + client::Client::get_collab_server_url(http.clone(), Some(release_channel)).await?; + crash_report_url.set_path("/crash"); + + for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] { + let mut children = smol::fs::read_dir(&dir).await?; + while let Some(child) = children.next().await { + let child = child?; + let Some(filename) = child + .path() + .file_name() + .map(|f| f.to_string_lossy().to_lowercase()) + else { + continue; + }; + + if !filename.starts_with("zed-") || !filename.ends_with(".ips") { + continue; } - Ok::<_, anyhow::Error>(()) - }) - .detach_and_log_err(cx); + + if filename <= last_uploaded { + continue; + } + + let body = smol::fs::read_to_string(&child.path()) + .await + .context("error reading crash file")?; + + let request = Request::post(&crash_report_url.to_string()) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header("Content-Type", "text/plain") + .header( + "Authorization", + format!("token {}", client::ZED_SECRET_CLIENT_TOKEN), + ) + .body(body.into())?; + + let response = http.send(request).await.context("error sending crash")?; + if !response.status().is_success() { + log::error!("Error uploading crash to server: {}", response.status()); + } + + if uploaded < filename { + uploaded = filename.clone(); + KEY_VALUE_STORE + .write_kvp(LAST_CRASH_UPLOADED.to_string(), filename) + .await?; + } + } + } + + Ok(()) } async fn load_login_shell_environment() -> Result<()> { From 345b983c8edd18acc346845dcdd1266e144a05a9 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 17 Jan 2024 20:46:20 -0700 Subject: [PATCH 301/334] Improve panic logging Send along every symbol for inlined frames, and also include the address (and if we have it filename + lineno) --- crates/zed/src/main.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index ce39549016438b82b3fd9150e10c8891caa6d0e4..6924b955f803e362707773a962ca63c88b5ddf30 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -542,7 +542,22 @@ fn init_panic_hook(app: &App, installation_id: Option, session_id: Strin let mut backtrace = backtrace .frames() .iter() - .filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?))) + .flat_map(|frame| { + frame.symbols().iter().filter_map(|symbol| { + let name = symbol.name()?; + let addr = symbol.addr()? as usize; + let position = if let (Some(path), Some(lineno)) = ( + symbol.filename().and_then(|path| path.file_name()), + symbol.lineno(), + ) { + format!("{}:{}", path.to_string_lossy(), lineno) + } else { + "?".to_string() + }; + + Some(format!("{:} ({:#x}) at {}", name, addr, position)) + }) + }) .collect::>(); // Strip out leading stack frames for rust panic-handling. From 1d3ca8eb5d77e2c37e646ee1322fe32c4eaab06d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 17 Jan 2024 21:18:07 -0700 Subject: [PATCH 302/334] Adjust APIs for simpler examples in blog post --- crates/gpui/src/app.rs | 12 +++++++++--- crates/live_kit_client/examples/test_app.rs | 4 ++-- crates/storybook/src/storybook.rs | 3 +-- crates/zed/src/main.rs | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8f7345ae16ced7e84cb145de81635b8c805e3fe6..477bea143916688b84571b2da3ad24b9bbec52cf 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -111,14 +111,20 @@ pub struct App(Rc); /// configured, you'll start the app with `App::run`. impl App { /// Builds an app with the given asset source. - pub fn production(asset_source: Arc) -> Self { + pub fn new() -> Self { Self(AppContext::new( current_platform(), - asset_source, + Arc::new(()), http::client(), )) } + /// Assign + pub fn with_assets(self, asset_source: impl AssetSource) -> Self { + self.0.borrow_mut().asset_source = Arc::new(asset_source); + self + } + /// Start the application. The provided callback will be called once the /// app is fully launched. pub fn run(self, on_finish_launching: F) @@ -1167,7 +1173,7 @@ impl Context for AppContext { type Result = T; /// Build an entity that is owned by the application. The given function will be invoked with - /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned + /// a `ModelContext` and must return an object representing the entity. A `Model` handle will be returned, /// which can be used to access the entity in a context. fn new_model( &mut self, diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index 9fc8aafd30c283df748796790964dab11151d9af..06f297083066a2e749d7ecc3d10f94d4a2167115 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -1,4 +1,4 @@ -use std::{sync::Arc, time::Duration}; +use std::time::Duration; use futures::StreamExt; use gpui::{actions, KeyBinding, Menu, MenuItem}; @@ -12,7 +12,7 @@ actions!(live_kit_client, [Quit]); fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - gpui::App::production(Arc::new(())).run(|cx| { + gpui::App::new().run(|cx| { #[cfg(any(test, feature = "test-support"))] println!("USING TEST LIVEKIT"); diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 7da1d67b307e660963c34297fc1fcfcca0c4222b..1c5ffb494bdb24dc794bce42aacc4a8265244b66 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -60,8 +60,7 @@ fn main() { }); let theme_name = args.theme.unwrap_or("One Dark".to_string()); - let asset_source = Arc::new(Assets); - gpui::App::production(asset_source).run(move |cx| { + gpui::App::new().with_assets(Assets).run(move |cx| { load_embedded_fonts(cx).unwrap(); let mut store = SettingsStore::default(); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 821668001c4fa42f757eea55b5e80361e0456e6d..82f608a87f38b544343c99ac2d54e066c3d08307 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -67,7 +67,7 @@ fn main() { } log::info!("========== starting zed =========="); - let app = App::production(Arc::new(Assets)); + let app = App::new().with_assets(Assets); let (installation_id, existing_installation_id_found) = app .background_executor() From bef1b8326557f843c1888a5edbd90e128389928b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 17 Jan 2024 21:18:17 -0700 Subject: [PATCH 303/334] Add ownership post example --- crates/gpui/examples/ownership_post.rs | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 crates/gpui/examples/ownership_post.rs diff --git a/crates/gpui/examples/ownership_post.rs b/crates/gpui/examples/ownership_post.rs new file mode 100644 index 0000000000000000000000000000000000000000..603b63b254644f808bebb0477d569a58f9dfea70 --- /dev/null +++ b/crates/gpui/examples/ownership_post.rs @@ -0,0 +1,34 @@ +use gpui::{prelude::*, App, AppContext, EventEmitter, Model, ModelContext}; + +struct Counter { + count: usize, +} + +fn main() { + App::new().run(|cx: &mut AppContext| { + let counter: Model = cx.new_model(|_cx| Counter { count: 0 }); + let observer = cx.new_model(|cx: &mut ModelContext| { + cx.observe(&counter, |observer, observed, cx| { + observer.count = observed.read(cx).count * 2; + }) + .detach(); + + Counter { + count: counter.read(cx).count * 2, + } + }); + + counter.update(cx, |counter, cx| { + counter.count += 1; + cx.notify(); + }); + + assert_eq!(observer.read(cx).count, 2); + }); +} + +struct Change { + delta: isize, +} + +impl EventEmitter for Counter {} From ef6f39d09002afbea8b870ee5b237424d97e1131 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 17 Jan 2024 22:53:53 -0700 Subject: [PATCH 304/334] Upload panics via zed.dev instead --- Cargo.lock | 11 --- Procfile | 1 + crates/client/src/client.rs | 10 -- crates/collab/.env.toml | 3 - crates/collab/Cargo.toml | 1 - crates/collab/k8s/collab.template.yml | 15 --- crates/collab/src/api.rs | 127 +------------------------ crates/collab/src/lib.rs | 3 - crates/collab/src/tests/test_server.rs | 3 - crates/zed/src/main.rs | 8 +- 10 files changed, 7 insertions(+), 175 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ba85eb14864a9a1af4c3015bdc3154cea01d3a82..4c3a4e8592e86cc3ce2c795baa9b1b62fd8b2045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1474,7 +1474,6 @@ dependencies = [ "env_logger", "envy", "file_finder", - "form-data-builder", "fs", "futures 0.3.28", "git", @@ -2691,16 +2690,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "form-data-builder" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ff8fb4527b05539a9f573ba2831a1127038a7b45eea385a338a63dc5ab6829" -dependencies = [ - "base64 0.13.1", - "rand 0.8.5", -] - [[package]] name = "form_urlencoded" version = "1.2.0" diff --git a/Procfile b/Procfile index 122ad5139fe1adb3cee4188f1abd0d0bb41f8042..3f42c3a9677477bd7dcd04ca61a749206559be40 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,4 @@ +web: cd ../zed.dev && PORT=3000 npm run dev collab: cd crates/collab && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve livekit: livekit-server --dev postgrest: postgrest crates/collab/admin_api.conf diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index da695f6dc402a99880c87482cc4914e066b38d88..189402308487870bee29da52c9333283ceb7fd1f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -969,16 +969,6 @@ impl Client { Url::parse(&collab_url).context("invalid rpc url") } - // todo: this should probably be cached (And/or done better) - pub async fn get_collab_server_url( - http: Arc, - release_channel: Option, - ) -> Result { - let mut url = Self::get_rpc_url(http, release_channel).await?; - url.set_path(""); - Ok(url) - } - fn establish_websocket_connection( self: &Arc, credentials: &Credentials, diff --git a/crates/collab/.env.toml b/crates/collab/.env.toml index 46140ae9b32e3c87afbd3db7cc15ff2a8aa86feb..01866012ea8a14c94835d0662318d3f8c4df67f5 100644 --- a/crates/collab/.env.toml +++ b/crates/collab/.env.toml @@ -2,14 +2,11 @@ DATABASE_URL = "postgres://postgres@localhost/zed" DATABASE_MAX_CONNECTIONS = 5 HTTP_PORT = 8080 API_TOKEN = "secret" -CLIENT_TOKEN = "618033988749894" INVITE_LINK_PREFIX = "http://localhost:3000/invites/" ZED_ENVIRONMENT = "development" LIVE_KIT_SERVER = "http://localhost:7880" LIVE_KIT_KEY = "devkey" LIVE_KIT_SECRET = "secret" -# SLACK_PANIC_CHANNEL = -# SLACK_API_KEY = # RUST_LOG=info # LOG_JSON=true diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index f703c88bc04c4a9033e2ef522781cebecdd88d3f..bc273cb12a9b893ac0e0b7d0b710416ea4ba37b8 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -58,7 +58,6 @@ tracing = "0.1.34" tracing-log = "0.1.3" tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } uuid.workspace = true -form-data-builder = "1.0.1" [dev-dependencies] audio = { path = "../audio" } diff --git a/crates/collab/k8s/collab.template.yml b/crates/collab/k8s/collab.template.yml index eb9f4d9172bd66497f2299dc0a987c5d6fabba38..120e5f592f62bfebd1a8c4f6b95d8c665f5c5577 100644 --- a/crates/collab/k8s/collab.template.yml +++ b/crates/collab/k8s/collab.template.yml @@ -90,11 +90,6 @@ spec: secretKeyRef: name: api key: token - - name: CLIENT_TOKEN - valueFrom: - secretKeyRef: - name: api - key: client_token - name: LIVE_KIT_SERVER valueFrom: secretKeyRef: @@ -110,16 +105,6 @@ spec: secretKeyRef: name: livekit key: secret - - name: SLACK_PANIC_CHANNEL - valueFrom: - secretKeyRef: - name: slack - key: api_key - - name: SLACK_API_KEY - valueFrom: - secretKeyRef: - name: slack - key: panic_channel - name: INVITE_LINK_PREFIX value: ${INVITE_LINK_PREFIX} - name: RUST_BACKTRACE diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index daca13fa85935cde15a0b95649a2cb77367f1de3..a28aeac9ab23dd293fbfbd9a7c851709855408d4 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -18,28 +18,19 @@ use serde::{Deserialize, Serialize}; use std::sync::Arc; use tower::ServiceBuilder; use tracing::instrument; -use util::{async_maybe, http::AsyncBody, ResultExt}; pub fn routes(rpc_server: Arc, state: Arc) -> Router { - let called_from_website = Router::new() + Router::new() .route("/user", get(get_authenticated_user)) .route("/users/:id/access_tokens", post(create_access_token)) .route("/panic", post(trace_panic)) .route("/rpc_server_snapshot", get(get_rpc_server_snapshot)) .layer( ServiceBuilder::new() - .layer(Extension(state.clone())) + .layer(Extension(state)) .layer(Extension(rpc_server)) .layer(middleware::from_fn(validate_api_token)), - ); - - let called_from_client = Router::new().route("/crash", post(trace_crash)).layer( - ServiceBuilder::new() - .layer(Extension(state)) - .layer(middleware::from_fn(validate_client_secret)), - ); - - called_from_website.merge(called_from_client) + ) } pub async fn validate_api_token(req: Request, next: Next) -> impl IntoResponse { @@ -73,37 +64,6 @@ pub async fn validate_api_token(req: Request, next: Next) -> impl IntoR Ok::<_, Error>(next.run(req).await) } -pub async fn validate_client_secret(req: Request, next: Next) -> impl IntoResponse { - let token = req - .headers() - .get(http::header::AUTHORIZATION) - .and_then(|header| header.to_str().ok()) - .ok_or_else(|| { - Error::Http( - StatusCode::BAD_REQUEST, - "missing authorization header".to_string(), - ) - })? - .strip_prefix("token ") - .ok_or_else(|| { - Error::Http( - StatusCode::BAD_REQUEST, - "invalid authorization header".to_string(), - ) - })?; - - let state = req.extensions().get::>().unwrap(); - - if token != state.config.client_token { - Err(Error::Http( - StatusCode::UNAUTHORIZED, - "invalid client secret".to_string(), - ))? - } - - Ok::<_, Error>(next.run(req).await) -} - #[derive(Debug, Deserialize)] struct AuthenticatedUserParams { github_user_id: Option, @@ -167,87 +127,6 @@ async fn trace_panic(panic: Json) -> Result<()> { Ok(()) } -/// IPSHeader is the first line of an .ips file (in JSON format) -/// https://developer.apple.com/documentation/xcode/interpreting-the-json-format-of-a-crash-report -#[derive(Debug, Serialize, Deserialize)] -struct IPSHeader { - timestamp: Option, - name: Option, - app_name: Option, - app_version: Option, - slice_uuid: Option, - build_version: Option, - platform: Option, - #[serde(rename = "bundleID")] - bundle_id: Option, - share_with_app_devs: Option, - is_first_party: Option, - bug_type: Option, - os_version: Option, - roots_installed: Option, - incident_id: Option, -} - -#[instrument(skip(content, app))] -async fn trace_crash(content: String, Extension(app): Extension>) -> Result<()> { - let Some(header) = content.split("\n").next() else { - return Err(Error::Http( - StatusCode::BAD_REQUEST, - "invalid .ips file".to_string(), - )); - }; - let header: IPSHeader = serde_json::from_slice(&header.as_bytes())?; - let text = content.as_str(); - - tracing::error!(app_version = %header.app_version.clone().unwrap_or_default(), - build_version = %header.build_version.unwrap_or_default(), - os_version = %header.os_version.unwrap_or_default(), - bundle_id = %header.bundle_id.clone().unwrap_or_default(), - text = %text, - "crash report"); - - async_maybe!({ - let api_key = app.config.slack_api_key.clone()?; - let channel = app.config.slack_panic_channel.clone()?; - - let mut body = form_data_builder::FormData::new(Vec::new()); - body.write_field("content", text).log_err()?; - body.write_field("channels", channel.as_str()).log_err()?; - body.write_field( - "filename", - format!("zed-crash-{}.ips", header.incident_id.unwrap_or_default()).as_str(), - ) - .log_err()?; - body.write_field( - "initial_comment", - format!( - "New crash in {} ({})", - header.bundle_id.unwrap_or_default(), - header.app_version.unwrap_or_default() - ) - .as_str(), - ) - .log_err()?; - let content_type = body.content_type_header(); - let body = AsyncBody::from(body.finish().log_err()?); - - let request = Request::post("https://slack.com/api/files.upload") - .header("Content-Type", content_type) - .header("Authorization", format!("Bearer {}", api_key)) - .body(body) - .log_err()?; - - let response = util::http::client().send(request).await.log_err()?; - if !response.status().is_success() { - tracing::error!(response = ?response, "failed to send crash report to slack"); - } - - Some(()) - }) - .await; - Ok(()) -} - async fn get_rpc_server_snapshot( Extension(rpc_server): Extension>, ) -> Result { diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index bf4270512e6ab4a6247edc000a56c59308a54837..aba9bd75d1f0aa9cc1849309dcb8f8db5b2ed9e3 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -101,9 +101,6 @@ pub struct Config { pub rust_log: Option, pub log_json: Option, pub zed_environment: Arc, - pub slack_api_key: Option, - pub slack_panic_channel: Option, - pub client_token: String, } impl Config { diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index d6ea536dd1a7fc72191028ab62548229c3a95ed0..cda0621cb32385a399fdfdaef51821dd531281b2 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -465,7 +465,6 @@ impl TestServer { database_url: "".into(), database_max_connections: 0, api_token: "".into(), - client_token: "".into(), invite_link_prefix: "".into(), live_kit_server: None, live_kit_key: None, @@ -473,8 +472,6 @@ impl TestServer { rust_log: None, log_json: None, zed_environment: "test".into(), - slack_api_key: None, - slack_panic_channel: None, }, }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 6924b955f803e362707773a962ca63c88b5ddf30..f67ba6cddba97c44e124b3050f9fe95d5f8ed7aa 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -710,12 +710,10 @@ async fn upload_previous_crashes( } let last_uploaded = KEY_VALUE_STORE .read_kvp(LAST_CRASH_UPLOADED)? - .unwrap_or("zed-2024-01-17-000000.ips".to_string()); // don't upload old crash reports from before we had this. + .unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this. let mut uploaded = last_uploaded.clone(); - let mut crash_report_url = - client::Client::get_collab_server_url(http.clone(), Some(release_channel)).await?; - crash_report_url.set_path("/crash"); + let crash_report_url = format!("{}/api/crash", &*client::ZED_SERVER_URL); for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] { let mut children = smol::fs::read_dir(&dir).await?; @@ -741,7 +739,7 @@ async fn upload_previous_crashes( .await .context("error reading crash file")?; - let request = Request::post(&crash_report_url.to_string()) + let request = Request::post(&crash_report_url) .redirect_policy(isahc::config::RedirectPolicy::Follow) .header("Content-Type", "text/plain") .header( From 4d9ff5c4ff3714fc4a8c0f86df7c57d8df799f01 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 17 Jan 2024 23:02:36 -0700 Subject: [PATCH 305/334] Simplify service discovery in development --- crates/client/src/client.rs | 15 +++++---------- script/zed-local | 3 ++- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 189402308487870bee29da52c9333283ceb7fd1f..75bcc2512aa5601b3ee2b0719c383e75bf6a1fca 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -52,6 +52,7 @@ pub use user::*; lazy_static! { pub static ref ZED_SERVER_URL: String = std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string()); + pub static ref ZED_RPC_URL: Option = std::env::var("ZED_RPC_URL").ok(); pub static ref IMPERSONATE_LOGIN: Option = std::env::var("ZED_IMPERSONATE") .ok() .and_then(|s| if s.is_empty() { None } else { Some(s) }); @@ -933,6 +934,10 @@ impl Client { http: Arc, release_channel: Option, ) -> Result { + if let Some(url) = &*ZED_RPC_URL { + return Url::parse(url).context("invalid rpc url"); + } + let mut url = format!("{}/rpc", *ZED_SERVER_URL); if let Some(preview_param) = release_channel.and_then(|channel| channel.release_query_param()) @@ -941,14 +946,6 @@ impl Client { url += preview_param; } let response = http.get(&url, Default::default(), false).await?; - - // Normally, ZED_SERVER_URL is set to the URL of zed.dev website. - // The website's /rpc endpoint redirects to a collab server's /rpc endpoint, - // which requires authorization via an HTTP header. - // - // For testing purposes, ZED_SERVER_URL can also set to the direct URL of - // of a collab server. In that case, a request to the /rpc endpoint will - // return an 'unauthorized' response. let collab_url = if response.status().is_redirection() { response .headers() @@ -957,8 +954,6 @@ impl Client { .to_str() .map_err(EstablishConnectionError::other)? .to_string() - } else if response.status() == StatusCode::UNAUTHORIZED { - url } else { Err(anyhow!( "unexpected /rpc response status {}", diff --git a/script/zed-local b/script/zed-local index 090fbd58760cd04779340be19d06a199d2a0a01d..f9a07dda6fbcc38074d20e3983ab23571e485231 100755 --- a/script/zed-local +++ b/script/zed-local @@ -111,7 +111,8 @@ setTimeout(() => { ZED_WINDOW_POSITION: positions[i], ZED_STATELESS: "1", ZED_ALWAYS_ACTIVE: "1", - ZED_SERVER_URL: "http://localhost:8080", + ZED_SERVER_URL: "http://localhost:3000", + ZED_RPC_URL: "http://localhost:8080/rpc", ZED_ADMIN_API_TOKEN: "secret", ZED_WINDOW_SIZE: `${instanceWidth},${instanceHeight}`, PATH: process.env.PATH, From b807e6fe8067f1288b771471763d58172ce3004e Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 18 Jan 2024 00:58:50 -0500 Subject: [PATCH 306/334] Use try_global() --- crates/auto_update/src/auto_update.rs | 37 ++++++++++--------- crates/client/src/telemetry.rs | 8 ++-- crates/copilot/src/copilot.rs | 6 +-- crates/feature_flags/src/feature_flags.rs | 16 +++----- crates/project_panel/src/file_associations.rs | 6 +-- crates/semantic_index/src/semantic_index.rs | 7 +--- crates/vim/src/mode_indicator.rs | 5 +-- crates/workspace/src/item.rs | 10 ++--- crates/workspace/src/workspace.rs | 11 ++---- crates/zed/src/main.rs | 16 ++++---- 10 files changed, 52 insertions(+), 70 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 06e445e3de96e4e03c560caee3fe4420dfbaed90..9f36665d871668920ed4ed8eb336f84fa6ba72d2 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -145,17 +145,16 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { let auto_updater = auto_updater.read(cx); let server_url = &auto_updater.server_url; let current_version = auto_updater.current_version; - if cx.has_global::() { - match cx.global::() { - ReleaseChannel::Dev => {} - ReleaseChannel::Nightly => {} - ReleaseChannel::Preview => { - cx.open_url(&format!("{server_url}/releases/preview/{current_version}")) - } - ReleaseChannel::Stable => { - cx.open_url(&format!("{server_url}/releases/stable/{current_version}")) - } - } + + if let Some(release_channel) = cx.try_global::() { + let channel = match release_channel { + ReleaseChannel::Preview => "preview", + ReleaseChannel::Stable => "stable", + _ => return, + }; + cx.open_url(&format!( + "{server_url}/releases/{channel}/{current_version}" + )) } } } @@ -257,11 +256,13 @@ impl AutoUpdater { "{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg" ); cx.update(|cx| { - if cx.has_global::() { - if let Some(param) = cx.global::().release_query_param() { - url_string += "&"; - url_string += param; - } + if let Some(param) = cx + .try_global::() + .map(|release_channel| release_channel.release_query_param()) + .flatten() + { + url_string += "&"; + url_string += param; } })?; @@ -313,8 +314,8 @@ impl AutoUpdater { let (installation_id, release_channel, telemetry) = cx.update(|cx| { let installation_id = cx.global::>().telemetry().installation_id(); let release_channel = cx - .has_global::() - .then(|| cx.global::().display_name()); + .try_global::() + .map(|release_channel| release_channel.display_name()); let telemetry = TelemetrySettings::get_global(cx).metrics; (installation_id, release_channel, telemetry) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 5ee039a8cb23c939351c4ff85283569fe4d0dd95..313133ebef217f68e2817d8643c0c9ffcff1de39 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -150,11 +150,9 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5); impl Telemetry { pub fn new(client: Arc, cx: &mut AppContext) -> Arc { - let release_channel = if cx.has_global::() { - Some(cx.global::().display_name()) - } else { - None - }; + let release_channel = cx + .try_global::() + .map(|release_channel| release_channel.display_name()); TelemetrySettings::register(cx); diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 89d1086c8e4b897c3527964289ff11810078a57c..91204b74d7a48fab15952525eb219f76aa85c631 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -308,11 +308,7 @@ impl EventEmitter for Copilot {} impl Copilot { pub fn global(cx: &AppContext) -> Option> { - if cx.has_global::>() { - Some(cx.global::>().clone()) - } else { - None - } + cx.try_global::>().map(|model| model.clone()) } fn start( diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index ea16ff3f7291fd838be45fd826e7c7504ba914fd..907c37ddcd9649abb4feb0260e49f79e13af105f 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -57,18 +57,14 @@ impl FeatureFlagAppExt for AppContext { } fn has_flag(&self) -> bool { - if self.has_global::() { - self.global::().has_flag(T::NAME) - } else { - false - } + self.try_global::() + .map(|flags| flags.has_flag(T::NAME)) + .unwrap_or(false) } fn is_staff(&self) -> bool { - if self.has_global::() { - return self.global::().staff; - } else { - false - } + self.try_global::() + .map(|flags| flags.staff) + .unwrap_or(false) } } diff --git a/crates/project_panel/src/file_associations.rs b/crates/project_panel/src/file_associations.rs index 0ddcfc9285eb31b7f5be50d57d6ef8dd14cebb42..783fae4c5257a177845aa6e4cfdab30d058e7394 100644 --- a/crates/project_panel/src/file_associations.rs +++ b/crates/project_panel/src/file_associations.rs @@ -42,7 +42,7 @@ impl FileAssociations { } pub fn get_icon(path: &Path, cx: &AppContext) -> Option> { - let this = cx.has_global::().then(|| cx.global::())?; + let this = cx.try_global::()?; // FIXME: Associate a type with the languages and have the file's language // override these associations @@ -58,7 +58,7 @@ impl FileAssociations { } pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option> { - let this = cx.has_global::().then(|| cx.global::())?; + let this = cx.try_global::()?; let key = if expanded { EXPANDED_DIRECTORY_TYPE @@ -72,7 +72,7 @@ impl FileAssociations { } pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Option> { - let this = cx.has_global::().then(|| cx.global::())?; + let this = cx.try_global::()?; let key = if expanded { EXPANDED_CHEVRON_TYPE diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index 801f02e600c1130eb5364e352f7f3823d6821f7a..a556986f9b1f9bda64706a8691cd329ead136086 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -275,11 +275,8 @@ pub struct SearchResult { impl SemanticIndex { pub fn global(cx: &mut AppContext) -> Option> { - if cx.has_global::>() { - Some(cx.global::>().clone()) - } else { - None - } + cx.try_global::>() + .map(|semantic_index| semantic_index.clone()) } pub fn authenticate(&mut self, cx: &mut AppContext) -> bool { diff --git a/crates/vim/src/mode_indicator.rs b/crates/vim/src/mode_indicator.rs index b669b16112874a89cd303499cc0d17eab6b99267..423ae0c4e17cef8dd8dfdead70c340dbd9cd1162 100644 --- a/crates/vim/src/mode_indicator.rs +++ b/crates/vim/src/mode_indicator.rs @@ -28,11 +28,10 @@ impl ModeIndicator { fn update_mode(&mut self, cx: &mut ViewContext) { // Vim doesn't exist in some tests - if !cx.has_global::() { + let Some(vim) = cx.try_global::() else { return; - } + }; - let vim = Vim::read(cx); if vim.enabled { self.mode = Some(vim.state().mode); } else { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 4f696e4a335040eebc10396262382f60c8f2821f..79742ee7322f4e760217fdee2af69256ca9edc82 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -586,13 +586,9 @@ impl ItemHandle for View { } fn to_followable_item_handle(&self, cx: &AppContext) -> Option> { - if cx.has_global::() { - let builders = cx.global::(); - let item = self.to_any(); - Some(builders.get(&item.entity_type())?.1(&item)) - } else { - None - } + let builders = cx.try_global::()?; + let item = self.to_any(); + Some(builders.get(&item.entity_type())?.1(&item)) } fn on_release( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e8589849f14dbe9148ff467480f5d4a5583d1ed7..20c8bfc94a8adffb1eeb680d95c4f6dc602b34f5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -616,8 +616,8 @@ impl Workspace { let modal_layer = cx.new_view(|_| ModalLayer::new()); let mut active_call = None; - if cx.has_global::>() { - let call = cx.global::>().clone(); + if let Some(call) = cx.try_global::>() { + let call = call.clone(); let mut subscriptions = Vec::new(); subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); active_call = Some((call, subscriptions)); @@ -3686,11 +3686,8 @@ impl WorkspaceStore { update: proto::update_followers::Variant, cx: &AppContext, ) -> Option<()> { - if !cx.has_global::>() { - return None; - } - - let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id(); + let active_call = cx.try_global::>()?; + let room_id = active_call.read(cx).room()?.read(cx).id(); let follower_ids: Vec<_> = self .followers .iter() diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 821668001c4fa42f757eea55b5e80361e0456e6d..a7c52e592fcb93089a4f88351d19d377ef9ce0bc 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -102,13 +102,15 @@ fn main() { let open_listener = listener.clone(); app.on_open_urls(move |urls, _| open_listener.open_urls(&urls)); app.on_reopen(move |cx| { - if cx.has_global::>() { - if let Some(app_state) = cx.global::>().upgrade() { - workspace::open_new(&app_state, cx, |workspace, cx| { - Editor::new_file(workspace, &Default::default(), cx) - }) - .detach(); - } + if let Some(app_state) = cx + .try_global::>() + .map(|app_state| app_state.upgrade()) + .flatten() + { + workspace::open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); } }); From aacb17ef38047e12548352d11b62402694891ce6 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 18 Jan 2024 09:49:02 +0200 Subject: [PATCH 307/334] Fix buffer search focus not working When the Deploy action is called in the buffer with the buffer search bar already deployed, the focus should be on the search bar. --- crates/search/src/buffer_search.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index a1f0d9773be242818fa31cdb26a5a0de9e532dcd..f4c9f7ef0f43e5afc1237428d007a2ec0cf050ee 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -638,6 +638,12 @@ impl BufferSearchBar { registrar.register_handler(|this, _: &editor::actions::Cancel, cx| { this.dismiss(&Dismiss, cx); }); + + // register deploy buffer search for both search bar states, since we want to focus into the search bar + // when the deploy action is triggered in the buffer. + registrar.register_handler(|this, deploy, cx| { + this.deploy(deploy, cx); + }); registrar.register_handler_for_dismissed_search(|this, deploy, cx| { this.deploy(deploy, cx); }) From ed28170d428063bb64b23287c333368b9b721190 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Jan 2024 10:04:38 +0100 Subject: [PATCH 308/334] Always synchronize terminal before rendering it Previously, we were trying not to synchronize the terminal too often because there could be multiple layout/paint calls prior to rendering a frame. Now that we perform a single render pass per frame, we can just synchronize the terminal state. Not doing so could make it seem like we're dropping frames. --- crates/terminal/src/terminal.rs | 35 ++------------------ crates/terminal_view/src/terminal_element.rs | 2 +- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 3a01f01ca89e03a35586ed325db69a93844ed270..0b87ed1d976daf247b7b84efe3280290602911f3 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -47,7 +47,7 @@ use std::{ os::unix::prelude::AsRawFd, path::PathBuf, sync::Arc, - time::{Duration, Instant}, + time::Duration, }; use thiserror::Error; @@ -385,8 +385,6 @@ impl TerminalBuilder { last_content: Default::default(), last_mouse: None, matches: Vec::new(), - last_synced: Instant::now(), - sync_task: None, selection_head: None, shell_fd: fd as u32, shell_pid, @@ -542,8 +540,6 @@ pub struct Terminal { last_mouse_position: Option>, pub matches: Vec>, pub last_content: TerminalContent, - last_synced: Instant, - sync_task: Option>, pub selection_head: Option, pub breadcrumb_text: String, shell_pid: u32, @@ -977,40 +973,15 @@ impl Terminal { self.input(paste_text); } - pub fn try_sync(&mut self, cx: &mut ModelContext) { + pub fn sync(&mut self, cx: &mut ModelContext) { let term = self.term.clone(); - - let mut terminal = if let Some(term) = term.try_lock_unfair() { - term - } else if self.last_synced.elapsed().as_secs_f32() > 0.25 { - term.lock_unfair() // It's been too long, force block - } else if let None = self.sync_task { - //Skip this frame - let delay = cx.background_executor().timer(Duration::from_millis(16)); - self.sync_task = Some(cx.spawn(|weak_handle, mut cx| async move { - delay.await; - if let Some(handle) = weak_handle.upgrade() { - handle - .update(&mut cx, |terminal, cx| { - terminal.sync_task.take(); - cx.notify(); - }) - .ok(); - } - })); - return; - } else { - //No lock and delayed rendering already scheduled, nothing to do - return; - }; - + let mut terminal = term.lock_unfair(); //Note that the ordering of events matters for event processing while let Some(e) = self.events.pop_front() { self.process_terminal_event(&e, &mut terminal, cx) } self.last_content = Self::make_content(&terminal, &self.last_content); - self.last_synced = Instant::now(); } fn make_content(term: &Term, last_content: &TerminalContent) -> TerminalContent { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 6298b4c16a07b47054430a6e2dcacd9e4f6e033e..746a3716b82adbc7cbd0addba7c4d1d024eabc6c 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -446,7 +446,7 @@ impl TerminalElement { let last_hovered_word = self.terminal.update(cx, |terminal, cx| { terminal.set_size(dimensions); - terminal.try_sync(cx); + terminal.sync(cx); if self.can_navigate_to_selected_word && terminal.can_navigate_to_selected_word() { terminal.last_content.last_hovered_word.clone() } else { From b6786d5e41e4dbe84cffe5fa8b6148370db8f539 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 18 Jan 2024 11:36:00 +0200 Subject: [PATCH 309/334] Use a proper action when clicking navigate forward button --- crates/workspace/src/pane.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 3e88469aa8593967e3ffea1cfd3ba0392f7e11f7..a9f1676c66c59da0482caebe8c9a4e3da1626804 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -195,7 +195,7 @@ struct NavHistoryState { next_timestamp: Arc, } -#[derive(Copy, Clone)] +#[derive(Debug, Copy, Clone)] pub enum NavigationMode { Normal, GoingBack, @@ -1462,7 +1462,7 @@ impl Pane { .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); - move |_, cx| view.update(cx, Self::navigate_backward) + move |_, cx| view.update(cx, Self::navigate_forward) }) .disabled(!self.can_navigate_forward()) .tooltip(|cx| Tooltip::for_action("Go Forward", &GoForward, cx)), From 559461923fba13d906f1e5135aa7d6c85dbe77fc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Jan 2024 10:40:15 +0100 Subject: [PATCH 310/334] Remove unused `PlatformAtlas::clear` method --- crates/gpui/src/platform.rs | 2 -- crates/gpui/src/platform/mac/metal_atlas.rs | 14 -------------- crates/gpui/src/platform/test/window.rs | 6 ------ 3 files changed, 22 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index dfb85104fec3b542b221b527dbe2d82b336af868..f43e96280f43ef1ad1031eda9cfc13bdd6250cd6 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -282,8 +282,6 @@ pub(crate) trait PlatformAtlas: Send + Sync { key: &AtlasKey, build: &mut dyn FnMut() -> Result<(Size, Cow<'a, [u8]>)>, ) -> Result; - - fn clear(&self); } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/gpui/src/platform/mac/metal_atlas.rs b/crates/gpui/src/platform/mac/metal_atlas.rs index 10ca53530e2ba16d80f07cfab1722ed0eecbebc3..d3caeba5222e6a4739fc63ef54358eb3589debbf 100644 --- a/crates/gpui/src/platform/mac/metal_atlas.rs +++ b/crates/gpui/src/platform/mac/metal_atlas.rs @@ -74,20 +74,6 @@ impl PlatformAtlas for MetalAtlas { Ok(tile) } } - - fn clear(&self) { - let mut lock = self.0.lock(); - lock.tiles_by_key.clear(); - for texture in &mut lock.monochrome_textures { - texture.clear(); - } - for texture in &mut lock.polychrome_textures { - texture.clear(); - } - for texture in &mut lock.path_textures { - texture.clear(); - } - } } impl MetalAtlasState { diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 5c8a3e5a59cf91b86d50c311c1beeccfd5ac2840..2f080bd7098bd42cf309c52e86df754e0d153a35 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -325,10 +325,4 @@ impl PlatformAtlas for TestAtlas { Ok(state.tiles[key].clone()) } - - fn clear(&self) { - let mut state = self.0.lock(); - state.tiles = HashMap::default(); - state.next_id = 0; - } } From 5e6d1a47b26098b2539e97b03955d76f118fc971 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 18 Jan 2024 10:59:32 +0100 Subject: [PATCH 311/334] Refactor LanguageSever::fake into FakeLanguageServer::new This is just moving code around and doesn't change behaviour, but it's something Julia and I bumped into yesterday while writing docs. --- crates/copilot/src/copilot.rs | 3 ++- crates/language/src/language.rs | 2 +- crates/lsp/src/lsp.rs | 39 ++++++++++++++++++--------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 91204b74d7a48fab15952525eb219f76aa85c631..f36567c6b9439d59dd92ffb61aebaf93059e926b 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -369,10 +369,11 @@ impl Copilot { #[cfg(any(test, feature = "test-support"))] pub fn fake(cx: &mut gpui::TestAppContext) -> (Model, lsp::FakeLanguageServer) { + use lsp::FakeLanguageServer; use node_runtime::FakeNodeRuntime; let (server, fake_server) = - LanguageServer::fake("copilot".into(), Default::default(), cx.to_async()); + FakeLanguageServer::new("copilot".into(), Default::default(), cx.to_async()); let http = util::http::FakeHttpClient::create(|_| async { unreachable!() }); let node_runtime = FakeNodeRuntime::new(); let this = cx.new_model(|cx| Self { diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 366d2b0098ca36437252044c50825bceaed96081..57b76eddadc69c10201d0f2ba2430a1ea4abb07d 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -951,7 +951,7 @@ impl LanguageRegistry { if language.fake_adapter.is_some() { let task = cx.spawn(|cx| async move { let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap(); - let (server, mut fake_server) = lsp::LanguageServer::fake( + let (server, mut fake_server) = lsp::FakeLanguageServer::new( fake_adapter.name.to_string(), fake_adapter.capabilities.clone(), cx.clone(), diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 80ab6b07d1d5869b343566b64a022115fe84f8ad..a70422008ccd86a4db70c33e4ac8cdface6c9217 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -972,30 +972,18 @@ pub struct FakeLanguageServer { } #[cfg(any(test, feature = "test-support"))] -impl LanguageServer { - pub fn full_capabilities() -> ServerCapabilities { - ServerCapabilities { - document_highlight_provider: Some(OneOf::Left(true)), - code_action_provider: Some(CodeActionProviderCapability::Simple(true)), - document_formatting_provider: Some(OneOf::Left(true)), - document_range_formatting_provider: Some(OneOf::Left(true)), - definition_provider: Some(OneOf::Left(true)), - type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), - ..Default::default() - } - } - +impl FakeLanguageServer { /// Construct a fake language server. - pub fn fake( + pub fn new( name: String, capabilities: ServerCapabilities, cx: AsyncAppContext, - ) -> (Self, FakeLanguageServer) { + ) -> (LanguageServer, FakeLanguageServer) { let (stdin_writer, stdin_reader) = async_pipe::pipe(); let (stdout_writer, stdout_reader) = async_pipe::pipe(); let (notifications_tx, notifications_rx) = channel::unbounded(); - let server = Self::new_internal( + let server = LanguageServer::new_internal( LanguageServerId(0), stdin_writer, stdout_reader, @@ -1008,7 +996,7 @@ impl LanguageServer { |_| {}, ); let fake = FakeLanguageServer { - server: Arc::new(Self::new_internal( + server: Arc::new(LanguageServer::new_internal( LanguageServerId(0), stdout_writer, stdin_reader, @@ -1053,6 +1041,21 @@ impl LanguageServer { } } +#[cfg(any(test, feature = "test-support"))] +impl LanguageServer { + pub fn full_capabilities() -> ServerCapabilities { + ServerCapabilities { + document_highlight_provider: Some(OneOf::Left(true)), + code_action_provider: Some(CodeActionProviderCapability::Simple(true)), + document_formatting_provider: Some(OneOf::Left(true)), + document_range_formatting_provider: Some(OneOf::Left(true)), + definition_provider: Some(OneOf::Left(true)), + type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)), + ..Default::default() + } + } +} + #[cfg(any(test, feature = "test-support"))] impl FakeLanguageServer { /// See [`LanguageServer::notify`]. @@ -1188,7 +1191,7 @@ mod tests { #[gpui::test] async fn test_fake(cx: &mut TestAppContext) { let (server, mut fake) = - LanguageServer::fake("the-lsp".to_string(), Default::default(), cx.to_async()); + FakeLanguageServer::new("the-lsp".to_string(), Default::default(), cx.to_async()); let (message_tx, message_rx) = channel::unbounded(); let (diagnostics_tx, diagnostics_rx) = channel::unbounded(); From 0a0921f88b4540f2e5cef0686667942bf20a5fa7 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:01:10 +0100 Subject: [PATCH 312/334] gpui: Bring back family and style names in font name suggestions --- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/text_system.rs | 12 +++++++++++- crates/gpui/src/text_system.rs | 19 +++++++++++-------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index f43e96280f43ef1ad1031eda9cfc13bdd6250cd6..e08d7a85521c9f24d4c25ab6decb1f3fb99d9882 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -201,6 +201,7 @@ pub trait PlatformDispatcher: Send + Sync { pub(crate) trait PlatformTextSystem: Send + Sync { fn add_fonts(&self, fonts: &[Arc>]) -> Result<()>; fn all_font_names(&self) -> Vec; + fn all_font_families(&self) -> Vec; fn font_id(&self, descriptor: &Font) -> Result; fn font_metrics(&self, font_id: FontId) -> FontMetrics; fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result>; diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index a77741074f13e1515198921b95a3a310e4d3d422..d11efa902ae7c270ff0331b74b1860032ff5f212 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -85,7 +85,9 @@ impl PlatformTextSystem for MacTextSystem { }; let mut names = BTreeSet::new(); for descriptor in descriptors.into_iter() { - names.insert(descriptor.display_name()); + names.insert(descriptor.font_name()); + names.insert(descriptor.family_name()); + names.insert(descriptor.style_name()); } if let Ok(fonts_in_memory) = self.0.read().memory_source.all_families() { names.extend(fonts_in_memory); @@ -93,6 +95,14 @@ impl PlatformTextSystem for MacTextSystem { names.into_iter().collect() } + fn all_font_families(&self) -> Vec { + self.0 + .read() + .system_source + .all_families() + .expect("core text should never return an error") + } + fn font_id(&self, font: &Font) -> Result { let lock = self.0.upgradable_read(); if let Some(font_id) = lock.font_selections.get(font) { diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 27d216dd50e673659e9873908d7a8f54364a256f..1c9de5ea0495f60fc8d219891ad8a0c7406ae9fe 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -13,7 +13,7 @@ use crate::{ SharedString, Size, UnderlineStyle, }; use anyhow::anyhow; -use collections::{FxHashMap, FxHashSet}; +use collections::{BTreeSet, FxHashMap, FxHashSet}; use core::fmt; use itertools::Itertools; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; @@ -66,15 +66,18 @@ impl TextSystem { } pub fn all_font_names(&self) -> Vec { - let mut families = self.platform_text_system.all_font_names(); - families.append( - &mut self - .fallback_font_stack + let mut names: BTreeSet<_> = self + .platform_text_system + .all_font_names() + .into_iter() + .collect(); + names.extend(self.platform_text_system.all_font_families().into_iter()); + names.extend( + self.fallback_font_stack .iter() - .map(|font| font.family.to_string()) - .collect(), + .map(|font| font.family.to_string()), ); - families + names.into_iter().collect() } pub fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { self.platform_text_system.add_fonts(fonts) From 7c5fdb3a44364c4b052ec2a42afafff1f2b693e5 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 18 Jan 2024 08:07:52 -0500 Subject: [PATCH 313/334] Clean up view_release_notes() --- crates/auto_update/src/auto_update.rs | 30 ++++++++++--------- crates/auto_update/src/update_notification.rs | 4 ++- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 9f36665d871668920ed4ed8eb336f84fa6ba72d2..3b8d1c6e61e2f42839930ac49311c8f60a38e341 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -93,7 +93,9 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo cx.observe_new_views(|workspace: &mut Workspace, _cx| { workspace.register_action(|_, action: &Check, cx| check(action, cx)); - workspace.register_action(|_, action, cx| view_release_notes(action, cx)); + workspace.register_action(|_, action, cx| { + view_release_notes(action, cx); + }); // @nate - code to trigger update notification on launch // todo!("remove this when Nate is done") @@ -140,23 +142,23 @@ pub fn check(_: &Check, cx: &mut WindowContext) { } } -pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) { - if let Some(auto_updater) = AutoUpdater::get(cx) { +pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<()> { + let auto_updater = AutoUpdater::get(cx)?; + let release_channel = cx.try_global::()?; + + if matches!( + release_channel, + ReleaseChannel::Stable | ReleaseChannel::Preview + ) { let auto_updater = auto_updater.read(cx); let server_url = &auto_updater.server_url; + let release_channel = release_channel.dev_name(); let current_version = auto_updater.current_version; - - if let Some(release_channel) = cx.try_global::() { - let channel = match release_channel { - ReleaseChannel::Preview => "preview", - ReleaseChannel::Stable => "stable", - _ => return, - }; - cx.open_url(&format!( - "{server_url}/releases/{channel}/{current_version}" - )) - } + let url = format!("{server_url}/releases/{release_channel}/{current_version}"); + cx.open_url(&url); } + + None } pub fn notify_of_any_new_update(cx: &mut ViewContext) -> Option<()> { diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index 985972da56364c646850fe7146f3323d06f56015..1562d90057199b14ccafe60bfa13ac0506a6dd27 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -40,7 +40,9 @@ impl Render for UpdateNotification { .id("notes") .child(Label::new("View the release notes")) .cursor_pointer() - .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)), + .on_click(|_, cx| { + crate::view_release_notes(&Default::default(), cx); + }), ) } } From b6d8665fc1bcf005278c6aea5b4dde9ab06debb2 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:07:53 +0100 Subject: [PATCH 314/334] pane: stop propagation of drag/click events in resizing handle This prevents focused editor from being scrolled while a pane is getting resized. Fixes: Mouse down to start an editor resize causes a scroll --- crates/workspace/src/pane_group.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index e631cd9c436b6918a974d2afdcc7ac9d4aa37520..476592f3745568e67fcbea9b44060cceea90370c 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -698,6 +698,7 @@ mod element { workspace .update(cx, |this, cx| this.schedule_serialize(cx)) .log_err(); + cx.stop_propagation(); cx.refresh(); } @@ -754,8 +755,10 @@ mod element { workspace .update(cx, |this, cx| this.schedule_serialize(cx)) .log_err(); + cx.refresh(); } + cx.stop_propagation(); } } }); From f9165938b0d25f2238f56895732f7aa47a56faed Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 18 Jan 2024 07:01:46 -0700 Subject: [PATCH 315/334] More adjustments for blog post --- crates/gpui/examples/ownership_post.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/gpui/examples/ownership_post.rs b/crates/gpui/examples/ownership_post.rs index 603b63b254644f808bebb0477d569a58f9dfea70..cd3b6264c36537ff61d84556201b801b887ab26b 100644 --- a/crates/gpui/examples/ownership_post.rs +++ b/crates/gpui/examples/ownership_post.rs @@ -4,12 +4,18 @@ struct Counter { count: usize, } +struct Change { + increment: usize, +} + +impl EventEmitter for Counter {} + fn main() { App::new().run(|cx: &mut AppContext| { let counter: Model = cx.new_model(|_cx| Counter { count: 0 }); - let observer = cx.new_model(|cx: &mut ModelContext| { - cx.observe(&counter, |observer, observed, cx| { - observer.count = observed.read(cx).count * 2; + let subscriber = cx.new_model(|cx: &mut ModelContext| { + cx.subscribe(&counter, |subscriber, _emitter, event, _cx| { + subscriber.count += event.increment * 2; }) .detach(); @@ -19,16 +25,11 @@ fn main() { }); counter.update(cx, |counter, cx| { - counter.count += 1; + counter.count += 2; cx.notify(); + cx.emit(Change { increment: 2 }); }); - assert_eq!(observer.read(cx).count, 2); + assert_eq!(subscriber.read(cx).count, 4); }); } - -struct Change { - delta: isize, -} - -impl EventEmitter for Counter {} From 130ea79c95611be311db2299d1acf78e01e5987e Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 18 Jan 2024 15:04:37 +0100 Subject: [PATCH 316/334] Fix focus and re-focus of project-wide search This fixes the issue of `Cmd-shift-f` not refocusing the search bar when a project-wide search already exists. It also fixes the handlers for "search in new" and "new search". Co-authored-by: Kirill --- crates/search/src/project_search.rs | 50 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index efb7af3879dcdff0141897b65fb0e3b952753609..c1ebcfe0a6a4a8214d6cbc76b3ebca5e0e1f5125 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -94,31 +94,29 @@ pub fn init(cx: &mut AppContext) { search_bar.select_next_match(action, cx) }, ); - register_workspace_action( - workspace, - move |search_bar, action: &SelectPrevMatch, cx| { - search_bar.select_prev_match(action, cx) - }, - ); - register_workspace_action_for_dismissed_search( - workspace, - move |workspace, action: &NewSearch, cx| { - ProjectSearchView::new_search(workspace, action, cx) - }, - ); - register_workspace_action_for_dismissed_search( - workspace, - move |workspace, action: &DeploySearch, cx| { - ProjectSearchView::deploy_search(workspace, action, cx) - }, - ); - register_workspace_action_for_dismissed_search( - workspace, - move |workspace, action: &SearchInNew, cx| { - ProjectSearchView::search_in_new(workspace, action, cx) - }, - ); + // Only handle search_in_new if there is a search present + register_workspace_action_for_present_search(workspace, |workspace, action, cx| { + ProjectSearchView::search_in_new(workspace, action, cx) + }); + + // Both on present and dismissed search, we need to unconditionally handle those actions to focus from the editor. + workspace.register_action(move |workspace, action: &DeploySearch, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + ProjectSearchView::deploy_search(workspace, action, cx); + cx.notify(); + }); + workspace.register_action(move |workspace, action: &NewSearch, cx| { + if workspace.has_active_modal(cx) { + cx.propagate(); + return; + } + ProjectSearchView::new_search(workspace, action, cx); + cx.notify(); + }); }) .detach(); } @@ -2057,7 +2055,7 @@ fn register_workspace_action( }); } -fn register_workspace_action_for_dismissed_search( +fn register_workspace_action_for_present_search( workspace: &mut Workspace, callback: fn(&mut Workspace, &A, &mut ViewContext), ) { @@ -2073,7 +2071,7 @@ fn register_workspace_action_for_dismissed_search( .toolbar() .read(cx) .item_of_type::() - .map(|search_bar| search_bar.read(cx).active_project_search.is_none()) + .map(|search_bar| search_bar.read(cx).active_project_search.is_some()) .unwrap_or(false); if should_notify { callback(workspace, action, cx); From 36ed5a31dfd1e591480be8c7550b97b479aa337b Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 18 Jan 2024 16:54:35 +0200 Subject: [PATCH 317/334] Wrap over picker's matches when reaching the end of the list --- crates/picker/src/picker.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 0af4675c9803505a4848049233fb02b60b308632..78d1c09e03a5e76fb86b57db248fcf1629a4d4ff 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -4,7 +4,7 @@ use gpui::{ FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext, }; -use std::{cmp, sync::Arc}; +use std::sync::Arc; use ui::{prelude::*, v_flex, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator}; use workspace::ModalView; @@ -103,7 +103,7 @@ impl Picker { let count = self.delegate.match_count(); if count > 0 { let index = self.delegate.selected_index(); - let ix = cmp::min(index + 1, count - 1); + let ix = if index == count - 1 { 0 } else { index + 1 }; self.delegate.set_selected_index(ix, cx); self.scroll_handle.scroll_to_item(ix); cx.notify(); @@ -114,7 +114,7 @@ impl Picker { let count = self.delegate.match_count(); if count > 0 { let index = self.delegate.selected_index(); - let ix = index.saturating_sub(1); + let ix = if index == 0 { count - 1 } else { index - 1 }; self.delegate.set_selected_index(ix, cx); self.scroll_handle.scroll_to_item(ix); cx.notify(); From b30efc9e81c75d5f02bd88930ec2d3cda8d79d11 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Thu, 18 Jan 2024 16:26:20 +0100 Subject: [PATCH 318/334] Fix missing icons: set svg_renderer when assets are updated Co-authored-by: Mikayla --- crates/gpui/src/app.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 477bea143916688b84571b2da3ad24b9bbec52cf..c7a6a92a17dce204412fa2c088132789d33b0cc4 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -121,7 +121,11 @@ impl App { /// Assign pub fn with_assets(self, asset_source: impl AssetSource) -> Self { - self.0.borrow_mut().asset_source = Arc::new(asset_source); + let mut context_lock = self.0.borrow_mut(); + let asset_source = Arc::new(asset_source); + context_lock.asset_source = asset_source.clone(); + context_lock.svg_renderer = SvgRenderer::new(asset_source); + drop(context_lock); self } From 9506fa461f49de679302809664916af99b97ce33 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:32:31 +0100 Subject: [PATCH 319/334] Start documenting display_map module --- crates/editor/src/display_map.rs | 37 ++++++++++++----------- crates/editor/src/display_map/fold_map.rs | 10 +++--- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 7ab5b0ff2abe75b6a35ab2ed120c2dee55e489af..7f29a7d04f536ac8de4af01cf2368e7be948b054 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,3 +1,22 @@ +//! This module defines where the text should be displayed in an [`Editor`][Editor]. +//! +//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement]. +//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when +//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that +//! we display as spaces and where to display custom blocks (like diagnostics). +//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up +//! of several smaller structures that form a hierarchy (starting at the bottom): +//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed. +//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded. +//! - [`TabMap`] that keeps track of hard tabs in a buffer. +//! - [`WrapMap`] that handles soft wrapping. +//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer. +//! - [`DisplayMap`] that adds background highlights to the regions of text. +//! Each one of those builds on top of preceding map. +//! +//! [Editor]: crate::Editor +//! [EditorElement]: crate::element::EditorElement + mod block_map; mod fold_map; mod inlay_map; @@ -971,24 +990,6 @@ impl ToDisplayPoint for Anchor { } } -pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterator { - let max_row = display_map.max_point().row(); - let start_row = display_row + 1; - let mut current = None; - std::iter::from_fn(move || { - if current == None { - current = Some(start_row); - } else { - current = Some(current.unwrap() + 1) - } - if current.unwrap() > max_row { - None - } else { - current - } - }) -} - #[cfg(test)] pub mod tests { use super::*; diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 7c6eeb444eb69ca7557a6d55ab4b4b1125b8b078..61b973cc6c70cd8ff733753c23f22474f58fce82 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -71,10 +71,10 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint { } } -pub struct FoldMapWriter<'a>(&'a mut FoldMap); +pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap); impl<'a> FoldMapWriter<'a> { - pub fn fold( + pub(crate) fn fold( &mut self, ranges: impl IntoIterator>, ) -> (FoldSnapshot, Vec) { @@ -129,7 +129,7 @@ impl<'a> FoldMapWriter<'a> { (self.0.snapshot.clone(), edits) } - pub fn unfold( + pub(crate) fn unfold( &mut self, ranges: impl IntoIterator>, inclusive: bool, @@ -178,14 +178,14 @@ impl<'a> FoldMapWriter<'a> { } } -pub struct FoldMap { +pub(crate) struct FoldMap { snapshot: FoldSnapshot, ellipses_color: Option, next_fold_id: FoldId, } impl FoldMap { - pub fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) { + pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) { let this = Self { snapshot: FoldSnapshot { folds: Default::default(), From f1df6318ba7fdba9a448aa18a8aa083e1d5bb4aa Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Jan 2024 09:06:13 -0700 Subject: [PATCH 320/334] Remove unused variable --- crates/zed/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f67ba6cddba97c44e124b3050f9fe95d5f8ed7aa..92435f68d29318b75cc1b2a80ee65e70767b9be4 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -615,13 +615,12 @@ fn init_panic_hook(app: &App, installation_id: Option, session_id: Strin fn upload_panics_and_crashes(http: Arc, cx: &mut AppContext) { let telemetry_settings = *client::TelemetrySettings::get_global(cx); - let release_channel = cx.global::().clone(); cx.background_executor() .spawn(async move { upload_previous_panics(http.clone(), telemetry_settings) .await .log_err(); - upload_previous_crashes(http, telemetry_settings, release_channel) + upload_previous_crashes(http, telemetry_settings) .await .log_err() }) @@ -703,7 +702,6 @@ static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED"; async fn upload_previous_crashes( http: Arc, telemetry_settings: client::TelemetrySettings, - release_channel: ReleaseChannel, ) -> Result<()> { if !telemetry_settings.diagnostics { return Ok(()); From 7b9e7fea4e8ad5195500b9014eb6987939ac626e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 18 Jan 2024 11:12:45 -0500 Subject: [PATCH 321/334] Use regular info color for speaker borders (#4126) This PR updates the speaker borders to use the regular `info` status color instead of the `info_border` color. This should provide more contrast and make it clearer as to who is speaking. Release Notes: - Made the active speakers' borders more visible when in a call. --- crates/collab_ui/src/collab_titlebar_item.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 01f0ccb179fd9f870bcad138aee2fd7a9d925987..ca988a9b1ad27556a988b020dad376e970838af1 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -486,7 +486,7 @@ impl CollabTitlebarItem { Avatar::new(user.avatar_uri.clone()) .grayscale(!is_present) .border_color(if is_speaking { - cx.theme().status().info_border + cx.theme().status().info } else { // We draw the border in a transparent color rather to avoid // the layout shift that would come with adding/removing the border. From 2835c9a972a082a4818ced141465f184d355049e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Jan 2024 10:40:43 -0700 Subject: [PATCH 322/334] Don't send follower events from other panes Co-Authored-By: Mikayla --- crates/workspace/src/item.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 79742ee7322f4e760217fdee2af69256ca9edc82..908ea1d168c22fcfbeeb0417bd40baec9f0ae1c9 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -448,11 +448,13 @@ impl ItemHandle for View { workspace.unfollow(&pane, cx); } - if item.add_event_to_update_proto( - event, - &mut *pending_update.borrow_mut(), - cx, - ) && !pending_update_scheduled.load(Ordering::SeqCst) + if item.focus_handle(cx).contains_focused(cx) + && item.add_event_to_update_proto( + event, + &mut *pending_update.borrow_mut(), + cx, + ) + && !pending_update_scheduled.load(Ordering::SeqCst) { pending_update_scheduled.store(true, Ordering::SeqCst); cx.defer({ From 2e35d900e0132a3a95529d6815ad867da00ae3ef Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2024 10:13:03 -0800 Subject: [PATCH 323/334] Invoke pane's focus_in handler when pane is focused directly --- crates/workspace/src/pane.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a1f3e6992aef51291fc66b9f4092e3dbd24c7be6..e8dbc746944e54b962fa99cee6872b36c4597360 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -239,6 +239,7 @@ impl Pane { let focus_handle = cx.focus_handle(); let subscriptions = vec![ + cx.on_focus(&focus_handle, Pane::focus_in), cx.on_focus_in(&focus_handle, Pane::focus_in), cx.on_focus_out(&focus_handle, Pane::focus_out), ]; From bc2302f72388c2d14ccd301c9dc613144b7c7a2a Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 18 Jan 2024 10:59:23 -0500 Subject: [PATCH 324/334] Store a z-index id per-layer Co-Authored-By: Nathan Sobo --- crates/collab_ui/src/face_pile.rs | 2 +- crates/gpui/src/style.rs | 8 +-- crates/gpui/src/styled.rs | 2 +- crates/gpui/src/window.rs | 71 +++++++++++++------------ crates/storybook/src/stories/z_index.rs | 4 +- crates/ui/src/styles/elevation.rs | 2 +- crates/workspace/src/workspace.rs | 2 +- 7 files changed, 46 insertions(+), 45 deletions(-) diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index fb6c59cc8079073acbb6b481e214b54619f9eea6..31132b298148944a95cde28918c51bcf94c33766 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -13,7 +13,7 @@ impl RenderOnce for FacePile { let isnt_last = ix < player_count - 1; div() - .z_index((player_count - ix) as u8) + .z_index((player_count - ix) as u16) .when(isnt_last, |div| div.neg_mr_1()) .child(player) }); diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index 095233280edefc0b11d85e3a4ee255f54c8da13d..bfc36ef6b116f355be5911151a32831cbd73f362 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -110,7 +110,7 @@ pub struct Style { /// The mouse cursor style shown when the mouse pointer is over an element. pub mouse_cursor: Option, - pub z_index: Option, + pub z_index: Option, #[cfg(debug_assertions)] pub debug: bool, @@ -386,7 +386,7 @@ impl Style { let background_color = self.background.as_ref().and_then(Fill::color); if background_color.map_or(false, |color| !color.is_transparent()) { - cx.with_z_index(0, |cx| { + cx.with_z_index(1, |cx| { let mut border_color = background_color.unwrap_or_default(); border_color.a = 0.; cx.paint_quad(quad( @@ -399,12 +399,12 @@ impl Style { }); } - cx.with_z_index(0, |cx| { + cx.with_z_index(2, |cx| { continuation(cx); }); if self.is_border_visible() { - cx.with_z_index(0, |cx| { + cx.with_z_index(3, |cx| { let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size); let border_widths = self.border_widths.to_pixels(rem_size); let max_border_width = border_widths.max(); diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index 0eba1771f52d47bde32f465a887e52547f3a89b2..e8800d1ce9dea7831e7c5ab186c96def45434d1b 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -12,7 +12,7 @@ pub trait Styled: Sized { gpui_macros::style_helpers!(); - fn z_index(mut self, z_index: u8) -> Self { + fn z_index(mut self, z_index: u16) -> Self { self.style().z_index = Some(z_index); self } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7453391ee721c1829e2140c4590f4bf67ce86187..c61379e814656b45e96de5fa506d90fe6cb6d43c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -43,30 +43,23 @@ use std::{ }; use util::{post_inc, ResultExt}; -const ACTIVE_DRAG_Z_INDEX: u8 = 1; +const ACTIVE_DRAG_Z_INDEX: u16 = 1; /// A global stacking order, which is created by stacking successive z-index values. /// Each z-index will always be interpreted in the context of its parent z-index. -#[derive(Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)] -pub struct StackingOrder { - #[deref] - #[deref_mut] - context_stack: SmallVec<[u8; 64]>, - id: u32, +#[derive(Debug, Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)] +pub struct StackingOrder(SmallVec<[StackingContext; 64]>); + +/// A single entry in a primitive's z-index stacking order +#[derive(Clone, Ord, PartialOrd, PartialEq, Eq, Default)] +pub struct StackingContext { + z_index: u16, + id: u16, } -impl std::fmt::Debug for StackingOrder { +impl std::fmt::Debug for StackingContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut stacks = self.context_stack.iter().peekable(); - write!(f, "[({}): ", self.id)?; - while let Some(z_index) = stacks.next() { - write!(f, "{z_index}")?; - if stacks.peek().is_some() { - write!(f, "->")?; - } - } - write!(f, "]")?; - Ok(()) + write!(f, "{{{}.{}}} ", self.z_index, self.id) } } @@ -315,8 +308,8 @@ pub(crate) struct Frame { pub(crate) scene: Scene, pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, pub(crate) z_index_stack: StackingOrder, - pub(crate) next_stacking_order_id: u32, - next_root_z_index: u8, + next_stacking_order_id: u16, + next_root_z_index: u16, content_mask_stack: Vec>, element_offset_stack: Vec>, requested_input_handler: Option, @@ -1105,7 +1098,11 @@ impl<'a> WindowContext<'a> { if level >= opaque_level { break; } - if opaque_level.starts_with(&[ACTIVE_DRAG_Z_INDEX]) { + if opaque_level + .first() + .map(|c| c.z_index == ACTIVE_DRAG_Z_INDEX) + .unwrap_or(false) + { continue; } @@ -2452,36 +2449,40 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { size: self.window().viewport_size, }, }; + + let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index); let new_stacking_order_id = post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); - let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index); + let new_context = StackingContext { + z_index: new_root_z_index, + id: new_stacking_order_id, + }; + let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack); - self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; - self.window_mut() - .next_frame - .z_index_stack - .push(new_root_z_index); + + self.window_mut().next_frame.z_index_stack.push(new_context); self.window_mut().next_frame.content_mask_stack.push(mask); let result = f(self); self.window_mut().next_frame.content_mask_stack.pop(); self.window_mut().next_frame.z_index_stack = old_stacking_order; + result } /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. - fn with_z_index(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R { + fn with_z_index(&mut self, z_index: u16, f: impl FnOnce(&mut Self) -> R) -> R { let new_stacking_order_id = post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); - let old_stacking_order_id = mem::replace( - &mut self.window_mut().next_frame.z_index_stack.id, - new_stacking_order_id, - ); - self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; - self.window_mut().next_frame.z_index_stack.push(z_index); + let new_context = StackingContext { + z_index, + id: new_stacking_order_id, + }; + + self.window_mut().next_frame.z_index_stack.push(new_context); let result = f(self); - self.window_mut().next_frame.z_index_stack.id = old_stacking_order_id; self.window_mut().next_frame.z_index_stack.pop(); + result } diff --git a/crates/storybook/src/stories/z_index.rs b/crates/storybook/src/stories/z_index.rs index b6e49bfae32b046242e96a5de34d30c3be806b94..63ee1af7591ee8a62f07f052b320db8a70077b9d 100644 --- a/crates/storybook/src/stories/z_index.rs +++ b/crates/storybook/src/stories/z_index.rs @@ -76,7 +76,7 @@ impl Styles for Div {} #[derive(IntoElement)] struct ZIndexExample { - z_index: u8, + z_index: u16, } impl RenderOnce for ZIndexExample { @@ -166,7 +166,7 @@ impl RenderOnce for ZIndexExample { } impl ZIndexExample { - pub fn new(z_index: u8) -> Self { + pub fn new(z_index: u16) -> Self { Self { z_index } } } diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index 0aa3786a279242c8a3a301b497a60ab0c2c537ec..c2605fd152df49d04db57d6784bc3a54aaefd80d 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -20,7 +20,7 @@ pub enum ElevationIndex { } impl ElevationIndex { - pub fn z_index(self) -> u8 { + pub fn z_index(self) -> u16 { match self { ElevationIndex::Background => 0, ElevationIndex::Surface => 42, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 20c8bfc94a8adffb1eeb680d95c4f6dc602b34f5..a8aaa403fbf5d4cc11af3abe10f567f049138035 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4334,7 +4334,7 @@ impl Element for DisconnectedOverlay { } fn paint(&mut self, bounds: Bounds, overlay: &mut Self::State, cx: &mut WindowContext) { - cx.with_z_index(u8::MAX, |cx| { + cx.with_z_index(u16::MAX, |cx| { cx.add_opaque_layer(bounds); overlay.paint(cx); }) From e992f84735a2ca166a7388d4eca69a6b4e6ee212 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2024 10:29:05 -0800 Subject: [PATCH 325/334] collab 0.37.0 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c3a4e8592e86cc3ce2c795baa9b1b62fd8b2045..62dbd027dd47c2d99e6f2295d63d6a8ce3b6ea8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1452,7 +1452,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.36.1" +version = "0.37.0" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index bc273cb12a9b893ac0e0b7d0b710416ea4ba37b8..9209d9ac2d3e24ffec20b628076e1c3709d92bb6 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.36.1" +version = "0.37.0" publish = false [[bin]] From 920eced1d5e4fa40284b24c79db43153e87b7c64 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Jan 2024 12:07:52 -0700 Subject: [PATCH 326/334] Fix right click handler for tabs Also, some fun test helpers Co-Authored-By: Mikayla --- crates/collab/src/tests/integration_tests.rs | 44 ++++++++++++++++++- crates/gpui/src/app/test_context.rs | 12 ++++- crates/gpui/src/elements/div.rs | 23 ++++++++++ crates/gpui/src/window.rs | 22 +++++++++- .../ui/src/components/button/button_like.rs | 2 +- .../ui/src/components/button/icon_button.rs | 6 ++- crates/ui/src/components/context_menu.rs | 1 + crates/ui/src/components/right_click_menu.rs | 12 +++-- crates/ui/src/components/tab.rs | 5 ++- 9 files changed, 116 insertions(+), 11 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index e68fd10d8d16b29300c3fa658596468df06be880..a8e52f4094c83ac79a29c9b30eae666566de96e2 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -7,7 +7,10 @@ use client::{User, RECEIVE_TIMEOUT}; use collections::{HashMap, HashSet}; use fs::{repository::GitFileStatus, FakeFs, Fs as _, RemoveOptions}; use futures::StreamExt as _; -use gpui::{AppContext, BackgroundExecutor, Model, TestAppContext}; +use gpui::{ + px, size, AppContext, BackgroundExecutor, Model, Modifiers, MouseButton, MouseDownEvent, + TestAppContext, +}; use language::{ language_settings::{AllLanguageSettings, Formatter}, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, @@ -5903,3 +5906,42 @@ async fn test_join_call_after_screen_was_shared( ); }); } + +#[gpui::test] +async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) { + let mut server = TestServer::start(cx.executor().clone()).await; + let client_a = server.create_client(cx, "user_a").await; + let (_workspace_a, cx) = client_a.build_test_workspace(cx).await; + + cx.simulate_resize(size(px(300.), px(300.))); + + cx.simulate_keystrokes("cmd-n cmd-n cmd-n"); + cx.update(|cx| cx.refresh()); + + let tab_bounds = cx.debug_bounds("TAB-2").unwrap(); + let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap(); + + assert!( + tab_bounds.intersects(&new_tab_button_bounds), + "Tab should overlap with the new tab button, if this is failing check if there's been a redesign!" + ); + + cx.simulate_event(MouseDownEvent { + button: MouseButton::Right, + position: new_tab_button_bounds.center(), + modifiers: Modifiers::default(), + click_count: 1, + }); + + // regression test that the right click menu for tabs does not open. + assert!(cx.debug_bounds("MENU_ITEM-Close").is_none()); + + let tab_bounds = cx.debug_bounds("TAB-1").unwrap(); + cx.simulate_event(MouseDownEvent { + button: MouseButton::Right, + position: tab_bounds.center(), + modifiers: Modifiers::default(), + click_count: 1, + }); + assert!(cx.debug_bounds("MENU_ITEM-Close").is_some()); +} diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index d95558f058a91bb4a7cd9a3ab347ee10584bffba..d11c1239dd576d9db944f605cad7d4140624feb9 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -2,7 +2,7 @@ use crate::{ Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, - AvailableSpace, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, + AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, InputEvent, Keystroke, Model, ModelContext, Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions, @@ -618,6 +618,16 @@ impl<'a> VisualTestContext { self.cx.simulate_input(self.window, input) } + /// Simulates the user resizing the window to the new size. + pub fn simulate_resize(&self, size: Size) { + self.simulate_window_resize(self.window, size) + } + + /// debug_bounds returns the bounds of the element with the given selector. + pub fn debug_bounds(&mut self, selector: &'static str) -> Option> { + self.update(|cx| cx.window.rendered_frame.debug_bounds.get(selector).copied()) + } + /// Draw an element to the window. Useful for simulating events or actions pub fn draw( &mut self, diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 74000da0512ffac35e4cf87c122bdecf08d67aed..aa912eadbe9c986969dd197927af8963a3c58d0c 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -416,6 +416,18 @@ pub trait InteractiveElement: Sized { self } + #[cfg(any(test, feature = "test-support"))] + fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self { + self.interactivity().debug_selector = Some(f()); + self + } + + #[cfg(not(any(test, feature = "test-support")))] + #[inline] + fn debug_selector(self, _: impl FnOnce() -> String) -> Self { + self + } + fn capture_any_mouse_down( mut self, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, @@ -911,6 +923,9 @@ pub struct Interactivity { #[cfg(debug_assertions)] pub location: Option>, + + #[cfg(any(test, feature = "test-support"))] + pub debug_selector: Option, } #[derive(Clone, Debug)] @@ -980,6 +995,14 @@ impl Interactivity { let style = self.compute_style(Some(bounds), element_state, cx); let z_index = style.z_index.unwrap_or(0); + #[cfg(any(feature = "test-support", test))] + if let Some(debug_selector) = &self.debug_selector { + cx.window + .next_frame + .debug_bounds + .insert(debug_selector.clone(), bounds); + } + let paint_hover_group_handler = |cx: &mut WindowContext| { let hover_group_bounds = self .group_hover_style diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7453391ee721c1829e2140c4590f4bf67ce86187..2329a5251ed010791e043bc08b70fb2e5e19d895 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -30,7 +30,7 @@ use std::{ borrow::{Borrow, BorrowMut, Cow}, cell::RefCell, collections::hash_map::Entry, - fmt::Debug, + fmt::{Debug, Display}, future::Future, hash::{Hash, Hasher}, marker::PhantomData, @@ -325,6 +325,9 @@ pub(crate) struct Frame { requested_cursor_style: Option, pub(crate) view_stack: Vec, pub(crate) reused_views: FxHashSet, + + #[cfg(any(test, feature = "test-support"))] + pub(crate) debug_bounds: collections::FxHashMap>, } impl Frame { @@ -348,6 +351,9 @@ impl Frame { requested_cursor_style: None, view_stack: Vec::new(), reused_views: FxHashSet::default(), + + #[cfg(any(test, feature = "test-support"))] + debug_bounds: FxHashMap::default(), } } @@ -3379,6 +3385,20 @@ pub enum ElementId { NamedInteger(SharedString, usize), } +impl Display for ElementId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ElementId::View(entity_id) => write!(f, "view-{}", entity_id)?, + ElementId::Integer(ix) => write!(f, "{}", ix)?, + ElementId::Name(name) => write!(f, "{}", name)?, + ElementId::FocusHandle(__) => write!(f, "FocusHandle")?, + ElementId::NamedInteger(s, i) => write!(f, "{}-{}", s, i)?, + } + + Ok(()) + } +} + impl ElementId { pub(crate) fn from_entity_id(entity_id: EntityId) -> Self { ElementId::View(entity_id) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index c2910acfc04bee67630c3d55b2ce2394559379f6..aafb33cd6f541a7573f7910fc559a8d170416689 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -293,7 +293,7 @@ impl ButtonSize { /// This is also used to build the prebuilt buttons. #[derive(IntoElement)] pub struct ButtonLike { - base: Div, + pub base: Div, id: ElementId, pub(super) style: ButtonStyle, pub(super) disabled: bool, diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index cc1e31b65cb0836be9a307107302ba8705597d6c..6de32c0eab29a22da541e2617d1adbe60f3656b2 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -24,14 +24,16 @@ pub struct IconButton { impl IconButton { pub fn new(id: impl Into, icon: IconName) -> Self { - Self { + let mut this = Self { base: ButtonLike::new(id), shape: IconButtonShape::Wide, icon, icon_size: IconSize::default(), icon_color: Color::Default, selected_icon: None, - } + }; + this.base.base = this.base.base.debug_selector(|| format!("ICON-{:?}", icon)); + this } pub fn shape(mut self, shape: IconButtonShape) -> Self { diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 4b6837799976579b6bc469753c51cb964e49e7b0..470483cc0a713cbfc53d1a9e7db343a604918a2e 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -303,6 +303,7 @@ impl Render for ContextMenu { .w_full() .justify_between() .child(label_element) + .debug_selector(|| format!("MENU_ITEM-{}", label)) .children(action.as_ref().and_then(|action| { KeyBinding::for_action(&**action, cx) .map(|binding| div().ml_1().child(binding)) diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index 55cdd93a5bee9d4be4c3f293a262b868b6b7825a..9d32073dbdb077614f1c23dda58ce0d4bae35ce8 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -1,9 +1,9 @@ use std::{cell::RefCell, rc::Rc}; use gpui::{ - overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element, ElementId, - IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, - View, VisualContext, WindowContext, + overlay, AnchorCorner, AnyElement, BorrowWindow, Bounds, DismissEvent, DispatchPhase, Element, + ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent, + ParentElement, Pixels, Point, View, VisualContext, WindowContext, }; pub struct RightClickMenu { @@ -136,10 +136,14 @@ impl Element for RightClickMenu { let child_layout_id = element_state.child_layout_id.clone(); let child_bounds = cx.layout_bounds(child_layout_id.unwrap()); + let interactive_bounds = InteractiveBounds { + bounds: bounds.intersect(&cx.content_mask().bounds), + stacking_order: cx.stacking_order().clone(), + }; cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { if phase == DispatchPhase::Bubble && event.button == MouseButton::Right - && bounds.contains(&event.position) + && interactive_bounds.visibly_contains(&event.position, cx) { cx.stop_propagation(); cx.prevent_default(); diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index ade939fdaabcddfcdc6ac0a22b0222ff4f2b4170..7f1fcca721b9524b8879f2c6051e7481dc8db6ab 100644 --- a/crates/ui/src/components/tab.rs +++ b/crates/ui/src/components/tab.rs @@ -37,8 +37,11 @@ pub struct Tab { impl Tab { pub fn new(id: impl Into) -> Self { + let id = id.into(); Self { - div: div().id(id), + div: div() + .id(id.clone()) + .debug_selector(|| format!("TAB-{}", id)), selected: false, position: TabPosition::First, close_side: TabCloseSide::End, From 4408d45f7a70fe057ece299269cdb813fe47d04a Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 18 Jan 2024 20:55:41 +0100 Subject: [PATCH 327/334] gpui: Always recompute layout on cache miss. --- crates/gpui/src/view.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 6426ac4c32f05e0765f2b5ca3cdb81ee603edc6e..e485aa3e57525118ea777f402a44d7d45928c5b6 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -321,10 +321,7 @@ impl Element for AnyView { } } - let mut element = state - .element - .take() - .unwrap_or_else(|| (self.request_layout)(self, cx).1); + let mut element = (self.request_layout)(self, cx).1; element.draw(bounds.origin, bounds.size.into(), cx); state.cache_key = Some(ViewCacheKey { From 77fc332d6ff2ae6fda705c8676f9454cd8f98d41 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2024 12:18:12 -0800 Subject: [PATCH 328/334] Make zed-local support opening 5 or 6 zed instances --- crates/collab/.admins.default.json | 9 ++++- script/zed-local | 57 ++++++++++++++++++------------ 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/crates/collab/.admins.default.json b/crates/collab/.admins.default.json index 6ee4d8726a303be4457078be9353402cbd712f20..75153ae06b306c30d41455e98acf4c381e120031 100644 --- a/crates/collab/.admins.default.json +++ b/crates/collab/.admins.default.json @@ -1 +1,8 @@ -["nathansobo", "as-cii", "maxbrunsfeld", "iamnbutler"] +[ + "nathansobo", + "as-cii", + "maxbrunsfeld", + "iamnbutler", + "mikayla-maki", + "JosephTLyons" +] diff --git a/script/zed-local b/script/zed-local index 4ae4013a4c3fe7b4eaee1b24b7ea8c5bc70a2259..b50a8dbb63f8098a568ab4166881c5ac1239c4c0 100755 --- a/script/zed-local +++ b/script/zed-local @@ -5,15 +5,15 @@ USAGE zed-local [options] [zed args] SUMMARY - Runs 1-4 instances of Zed using a locally-running collaboration server. + Runs 1-6 instances of Zed using a locally-running collaboration server. Each instance of Zed will be signed in as a different user specified in either \`.admins.json\` or \`.admins.default.json\`. OPTIONS - --help Print this help message - --release Build Zed in release mode - -2, -3, -4 Spawn 2, 3, or 4 Zed instances, with their windows tiled. - --top Arrange the Zed windows so they take up the top half of the screen. + --help Print this help message + --release Build Zed in release mode + -2, -3, -4, ... Spawn multiple Zed instances, with their windows tiled. + --top Arrange the Zed windows so they take up the top half of the screen. `.trim(); const { spawn, execFileSync } = require("child_process"); @@ -25,7 +25,9 @@ try { const customUsers = require("../crates/collab/.admins.json"); assert(customUsers.length > 0); assert(customUsers.every((user) => typeof user === "string")); - users.splice(0, 0, ...customUsers); + users = customUsers.concat( + defaultUsers.filter((user) => !customUsers.includes(user)), + ); } catch (_) {} const RESOLUTION_REGEX = /(\d+) x (\d+)/; @@ -77,28 +79,34 @@ if (isTop) { } // Determine the window size for each instance -let instanceWidth = screenWidth; -let instanceHeight = screenHeight; -if (instanceCount > 1) { - instanceWidth = Math.floor(screenWidth / 2); - if (instanceCount > 2) { - instanceHeight = Math.floor(screenHeight / 2); - } +let rows; +let columns; +switch (instanceCount) { + case 1: + [rows, columns] = [1, 1]; + break; + case 2: + [rows, columns] = [1, 2]; + break; + case 3: + case 4: + [rows, columns] = [2, 2]; + break; + case 5: + case 6: + [rows, columns] = [2, 3]; + break; } +const instanceWidth = Math.floor(screenWidth / columns); +const instanceHeight = Math.floor(screenHeight / rows); + // If a user is specified, make sure it's first in the list const user = process.env.ZED_IMPERSONATE; if (user) { users = [user].concat(users.filter((u) => u !== user)); } -const positions = [ - "0,0", - `${instanceWidth},0`, - `0,${instanceHeight}`, - `${instanceWidth},${instanceHeight}`, -]; - let buildArgs = ["build"]; let zedBinary = "target/debug/Zed"; if (isReleaseMode) { @@ -109,16 +117,21 @@ if (isReleaseMode) { execFileSync("cargo", buildArgs, { stdio: "inherit" }); setTimeout(() => { for (let i = 0; i < instanceCount; i++) { + const row = Math.floor(i / columns); + const column = i % columns; + const position = [column * instanceWidth, row * instanceHeight].join(","); + const size = [instanceWidth, instanceHeight].join(","); + spawn(zedBinary, i == 0 ? args : [], { stdio: "inherit", env: { ZED_IMPERSONATE: users[i], - ZED_WINDOW_POSITION: positions[i], + ZED_WINDOW_POSITION: position, ZED_STATELESS: "1", ZED_ALWAYS_ACTIVE: "1", ZED_SERVER_URL: "http://localhost:8080", ZED_ADMIN_API_TOKEN: "secret", - ZED_WINDOW_SIZE: `${instanceWidth},${instanceHeight}`, + ZED_WINDOW_SIZE: size, PATH: process.env.PATH, RUST_LOG: process.env.RUST_LOG || "info", }, From 4e118f3dd7b178fb6f22ab44d2f1e8828665266d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Jan 2024 13:17:52 -0700 Subject: [PATCH 329/334] Fix some cases of broken repeat in vim --- crates/vim/src/editor_events.rs | 7 +++---- crates/vim/src/normal/repeat.rs | 13 +++++++++++++ crates/vim/src/test/neovim_backed_test_context.rs | 2 ++ crates/vim/test_data/test_repeat_over_blur.json | 11 +++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 crates/vim/test_data/test_repeat_over_blur.json diff --git a/crates/vim/src/editor_events.rs b/crates/vim/src/editor_events.rs index e4057792796cad86d7fe31ff01b78ea9434ae102..e325c529a7d70ff340ff92ea788552a3197bacae 100644 --- a/crates/vim/src/editor_events.rs +++ b/crates/vim/src/editor_events.rs @@ -1,6 +1,6 @@ -use crate::Vim; +use crate::{insert::NormalBefore, Vim}; use editor::{Editor, EditorEvent}; -use gpui::{AppContext, Entity, EntityId, View, ViewContext, WindowContext}; +use gpui::{Action, AppContext, Entity, EntityId, View, ViewContext, WindowContext}; pub fn init(cx: &mut AppContext) { cx.observe_new_views(|_, cx: &mut ViewContext| { @@ -34,8 +34,7 @@ fn focused(editor: View, cx: &mut WindowContext) { fn blurred(editor: View, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { - vim.workspace_state.recording = false; - vim.workspace_state.recorded_actions.clear(); + vim.stop_recording_immediately(NormalBefore.boxed_clone()); if let Some(previous_editor) = vim.active_editor.clone() { if previous_editor .upgrade() diff --git a/crates/vim/src/normal/repeat.rs b/crates/vim/src/normal/repeat.rs index a2587c6f003a57f0199dfd3b62bb6d6a1d1c2139..796cfce7a3c5bb3091996d0a8c72d76c4379abfa 100644 --- a/crates/vim/src/normal/repeat.rs +++ b/crates/vim/src/normal/repeat.rs @@ -493,4 +493,17 @@ mod test { cx.simulate_keystrokes(["escape"]); cx.assert_state("ˇjhello\n", Mode::Normal); } + + #[gpui::test] + async fn test_repeat_over_blur(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state("ˇhello hello hello\n").await; + cx.simulate_shared_keystrokes(["c", "f", "o", "x", "escape"]) + .await; + cx.assert_shared_state("ˇx hello hello\n").await; + cx.simulate_shared_keystrokes([":", "escape"]).await; + cx.simulate_shared_keystrokes(["."]).await; + cx.assert_shared_state("ˇx hello\n").await; + } } diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index fe5c5db62f831a3725e753ef4df3d448c28c4e68..384722286cdfa3768320c0810655c4ca06c9b012 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -62,6 +62,8 @@ pub struct NeovimBackedTestContext { impl NeovimBackedTestContext { pub async fn new(cx: &mut gpui::TestAppContext) -> NeovimBackedTestContext { + #[cfg(feature = "neovim")] + cx.executor().allow_parking(); // rust stores the name of the test on the current thread. // We use this to automatically name a file that will store // the neovim connection's requests/responses so that we can diff --git a/crates/vim/test_data/test_repeat_over_blur.json b/crates/vim/test_data/test_repeat_over_blur.json new file mode 100644 index 0000000000000000000000000000000000000000..3929711e49327db2e47c0c6fdcbe56ebff5dbd09 --- /dev/null +++ b/crates/vim/test_data/test_repeat_over_blur.json @@ -0,0 +1,11 @@ +{"Put":{"state":"ˇhello hello hello\n"}} +{"Key":"c"} +{"Key":"f"} +{"Key":"o"} +{"Key":"x"} +{"Key":"escape"} +{"Get":{"state":"ˇx hello hello\n","mode":"Normal"}} +{"Key":":"} +{"Key":"escape"} +{"Key":"."} +{"Get":{"state":"ˇx hello\n","mode":"Normal"}} From 6c2da0d25b7c987ed1015ff95daa6e9e8ea980cd Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 18 Jan 2024 13:28:23 -0700 Subject: [PATCH 330/334] Revert "Store a z-index id per-layer (#4128)" This reverts commit 28a23372185c7a30c7790085a6a010c3cad6ad09, reversing changes made to e992f84735a2ca166a7388d4eca69a6b4e6ee212. --- crates/collab_ui/src/face_pile.rs | 2 +- crates/gpui/src/style.rs | 8 +-- crates/gpui/src/styled.rs | 2 +- crates/gpui/src/window.rs | 71 ++++++++++++------------- crates/storybook/src/stories/z_index.rs | 4 +- crates/ui/src/styles/elevation.rs | 2 +- crates/workspace/src/workspace.rs | 2 +- 7 files changed, 45 insertions(+), 46 deletions(-) diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index 31132b298148944a95cde28918c51bcf94c33766..fb6c59cc8079073acbb6b481e214b54619f9eea6 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -13,7 +13,7 @@ impl RenderOnce for FacePile { let isnt_last = ix < player_count - 1; div() - .z_index((player_count - ix) as u16) + .z_index((player_count - ix) as u8) .when(isnt_last, |div| div.neg_mr_1()) .child(player) }); diff --git a/crates/gpui/src/style.rs b/crates/gpui/src/style.rs index bfc36ef6b116f355be5911151a32831cbd73f362..095233280edefc0b11d85e3a4ee255f54c8da13d 100644 --- a/crates/gpui/src/style.rs +++ b/crates/gpui/src/style.rs @@ -110,7 +110,7 @@ pub struct Style { /// The mouse cursor style shown when the mouse pointer is over an element. pub mouse_cursor: Option, - pub z_index: Option, + pub z_index: Option, #[cfg(debug_assertions)] pub debug: bool, @@ -386,7 +386,7 @@ impl Style { let background_color = self.background.as_ref().and_then(Fill::color); if background_color.map_or(false, |color| !color.is_transparent()) { - cx.with_z_index(1, |cx| { + cx.with_z_index(0, |cx| { let mut border_color = background_color.unwrap_or_default(); border_color.a = 0.; cx.paint_quad(quad( @@ -399,12 +399,12 @@ impl Style { }); } - cx.with_z_index(2, |cx| { + cx.with_z_index(0, |cx| { continuation(cx); }); if self.is_border_visible() { - cx.with_z_index(3, |cx| { + cx.with_z_index(0, |cx| { let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size); let border_widths = self.border_widths.to_pixels(rem_size); let max_border_width = border_widths.max(); diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index e8800d1ce9dea7831e7c5ab186c96def45434d1b..0eba1771f52d47bde32f465a887e52547f3a89b2 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -12,7 +12,7 @@ pub trait Styled: Sized { gpui_macros::style_helpers!(); - fn z_index(mut self, z_index: u16) -> Self { + fn z_index(mut self, z_index: u8) -> Self { self.style().z_index = Some(z_index); self } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index acb07b9f914bc308aaa7e123e420c4a4512cb561..2329a5251ed010791e043bc08b70fb2e5e19d895 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -43,23 +43,30 @@ use std::{ }; use util::{post_inc, ResultExt}; -const ACTIVE_DRAG_Z_INDEX: u16 = 1; +const ACTIVE_DRAG_Z_INDEX: u8 = 1; /// A global stacking order, which is created by stacking successive z-index values. /// Each z-index will always be interpreted in the context of its parent z-index. -#[derive(Debug, Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)] -pub struct StackingOrder(SmallVec<[StackingContext; 64]>); - -/// A single entry in a primitive's z-index stacking order -#[derive(Clone, Ord, PartialOrd, PartialEq, Eq, Default)] -pub struct StackingContext { - z_index: u16, - id: u16, +#[derive(Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)] +pub struct StackingOrder { + #[deref] + #[deref_mut] + context_stack: SmallVec<[u8; 64]>, + id: u32, } -impl std::fmt::Debug for StackingContext { +impl std::fmt::Debug for StackingOrder { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{{{}.{}}} ", self.z_index, self.id) + let mut stacks = self.context_stack.iter().peekable(); + write!(f, "[({}): ", self.id)?; + while let Some(z_index) = stacks.next() { + write!(f, "{z_index}")?; + if stacks.peek().is_some() { + write!(f, "->")?; + } + } + write!(f, "]")?; + Ok(()) } } @@ -308,8 +315,8 @@ pub(crate) struct Frame { pub(crate) scene: Scene, pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds)>, pub(crate) z_index_stack: StackingOrder, - next_stacking_order_id: u16, - next_root_z_index: u16, + pub(crate) next_stacking_order_id: u32, + next_root_z_index: u8, content_mask_stack: Vec>, element_offset_stack: Vec>, requested_input_handler: Option, @@ -1104,11 +1111,7 @@ impl<'a> WindowContext<'a> { if level >= opaque_level { break; } - if opaque_level - .first() - .map(|c| c.z_index == ACTIVE_DRAG_Z_INDEX) - .unwrap_or(false) - { + if opaque_level.starts_with(&[ACTIVE_DRAG_Z_INDEX]) { continue; } @@ -2455,40 +2458,36 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { size: self.window().viewport_size, }, }; - - let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index); let new_stacking_order_id = post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); - let new_context = StackingContext { - z_index: new_root_z_index, - id: new_stacking_order_id, - }; - + let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index); let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack); - - self.window_mut().next_frame.z_index_stack.push(new_context); + self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; + self.window_mut() + .next_frame + .z_index_stack + .push(new_root_z_index); self.window_mut().next_frame.content_mask_stack.push(mask); let result = f(self); self.window_mut().next_frame.content_mask_stack.pop(); self.window_mut().next_frame.z_index_stack = old_stacking_order; - result } /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. - fn with_z_index(&mut self, z_index: u16, f: impl FnOnce(&mut Self) -> R) -> R { + fn with_z_index(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R { let new_stacking_order_id = post_inc(&mut self.window_mut().next_frame.next_stacking_order_id); - let new_context = StackingContext { - z_index, - id: new_stacking_order_id, - }; - - self.window_mut().next_frame.z_index_stack.push(new_context); + let old_stacking_order_id = mem::replace( + &mut self.window_mut().next_frame.z_index_stack.id, + new_stacking_order_id, + ); + self.window_mut().next_frame.z_index_stack.id = new_stacking_order_id; + self.window_mut().next_frame.z_index_stack.push(z_index); let result = f(self); + self.window_mut().next_frame.z_index_stack.id = old_stacking_order_id; self.window_mut().next_frame.z_index_stack.pop(); - result } diff --git a/crates/storybook/src/stories/z_index.rs b/crates/storybook/src/stories/z_index.rs index 63ee1af7591ee8a62f07f052b320db8a70077b9d..b6e49bfae32b046242e96a5de34d30c3be806b94 100644 --- a/crates/storybook/src/stories/z_index.rs +++ b/crates/storybook/src/stories/z_index.rs @@ -76,7 +76,7 @@ impl Styles for Div {} #[derive(IntoElement)] struct ZIndexExample { - z_index: u16, + z_index: u8, } impl RenderOnce for ZIndexExample { @@ -166,7 +166,7 @@ impl RenderOnce for ZIndexExample { } impl ZIndexExample { - pub fn new(z_index: u16) -> Self { + pub fn new(z_index: u8) -> Self { Self { z_index } } } diff --git a/crates/ui/src/styles/elevation.rs b/crates/ui/src/styles/elevation.rs index c2605fd152df49d04db57d6784bc3a54aaefd80d..0aa3786a279242c8a3a301b497a60ab0c2c537ec 100644 --- a/crates/ui/src/styles/elevation.rs +++ b/crates/ui/src/styles/elevation.rs @@ -20,7 +20,7 @@ pub enum ElevationIndex { } impl ElevationIndex { - pub fn z_index(self) -> u16 { + pub fn z_index(self) -> u8 { match self { ElevationIndex::Background => 0, ElevationIndex::Surface => 42, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a8aaa403fbf5d4cc11af3abe10f567f049138035..20c8bfc94a8adffb1eeb680d95c4f6dc602b34f5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4334,7 +4334,7 @@ impl Element for DisconnectedOverlay { } fn paint(&mut self, bounds: Bounds, overlay: &mut Self::State, cx: &mut WindowContext) { - cx.with_z_index(u16::MAX, |cx| { + cx.with_z_index(u8::MAX, |cx| { cx.add_opaque_layer(bounds); overlay.paint(cx); }) From 547aab2fea87173326154138bb0e08336cdc1e2c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 18 Jan 2024 16:38:14 -0500 Subject: [PATCH 331/334] Style the reset key state for the assistant panel (#4136) This PR styles the reset key state for the assistant panel. Community issue: https://github.com/zed-industries/community/issues/2429 Screenshot 2024-01-18 at 4 28 32 PM Release Notes: - Fixed the reset key screen for the assistant panel. --- crates/assistant/src/assistant_panel.rs | 85 +++++++++++++++++++------ 1 file changed, 66 insertions(+), 19 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 93c0954b1710312b1f3f27b0c43fb25564c6046e..f2988b907c07cddba5c1646178762e8b28a508a5 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -920,6 +920,39 @@ impl AssistantPanel { self.editors.get(self.active_editor_index?) } + fn render_api_key_editor( + &self, + editor: &View, + cx: &mut ViewContext, + ) -> impl IntoElement { + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: if editor.read(cx).read_only(cx) { + cx.theme().colors().text_disabled + } else { + cx.theme().colors().text + }, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features, + font_size: rems(0.875).into(), + font_weight: FontWeight::NORMAL, + font_style: FontStyle::Normal, + line_height: relative(1.3).into(), + background_color: None, + underline: None, + white_space: WhiteSpace::Normal, + }; + EditorElement::new( + &editor, + EditorStyle { + background: cx.theme().colors().editor_background, + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + } + fn render_hamburger_button(cx: &mut ViewContext) -> impl IntoElement { IconButton::new("hamburger_button", IconName::Menu) .on_click(cx.listener(|this, _event, cx| { @@ -1091,28 +1124,42 @@ fn build_api_key_editor(cx: &mut ViewContext) -> View { impl Render for AssistantPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { if let Some(api_key_editor) = self.api_key_editor.clone() { + const INSTRUCTIONS: [&'static str; 5] = [ + "To use the assistant panel or inline assistant, you need to add your OpenAI API key.", + " - You can create an API key at: platform.openai.com/api-keys", + " - Having a subscription for another service like GitHub Copilot won't work.", + " ", + "Paste your OpenAI API key and press Enter to use the assistant:" + ]; + v_flex() + .p_4() + .size_full() .on_action(cx.listener(AssistantPanel::save_credentials)) .track_focus(&self.focus_handle) - .child(Label::new( - "To use the assistant panel or inline assistant, you need to add your OpenAI api key.", - )) - .child(Label::new( - " - Having a subscription for another service like GitHub Copilot won't work." - )) - .child(Label::new( - " - You can create a api key at: platform.openai.com/api-keys" - )) - .child(Label::new( - " " - )) - .child(Label::new( - "Paste your OpenAI API key and press Enter to use the assistant" - )) - .child(api_key_editor) - .child(Label::new( - "Click on the Z button in the status bar to close this panel." - )) + .children( + INSTRUCTIONS.map(|instruction| Label::new(instruction).size(LabelSize::Small)), + ) + .child( + h_flex() + .w_full() + .my_2() + .px_2() + .py_1() + .bg(cx.theme().colors().editor_background) + .rounded_md() + .child(self.render_api_key_editor(&api_key_editor, cx)), + ) + .child( + h_flex() + .gap_2() + .child(Label::new("Click on").size(LabelSize::Small)) + .child(Icon::new(IconName::Ai).size(IconSize::XSmall)) + .child( + Label::new("in the status bar to close this panel.") + .size(LabelSize::Small), + ), + ) } else { let header = TabBar::new("assistant_header") .start_child( From 2443ee6faf77d02dac3dc6af94f8b0ddb89c9c2e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2024 13:42:11 -0800 Subject: [PATCH 332/334] Account for titlebar when tiling windows in zed-local --- script/zed-local | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/script/zed-local b/script/zed-local index b50a8dbb63f8098a568ab4166881c5ac1239c4c0..04679f39af8ed31233451b14c3ac1b40b2172f47 100755 --- a/script/zed-local +++ b/script/zed-local @@ -71,8 +71,9 @@ const mainDisplayResolution = if (!mainDisplayResolution) { throw new Error("Could not parse screen resolution"); } +const titleBarHeight = 24; const screenWidth = parseInt(mainDisplayResolution[1]); -let screenHeight = parseInt(mainDisplayResolution[2]); +let screenHeight = parseInt(mainDisplayResolution[2]) - titleBarHeight; if (isTop) { screenHeight = Math.floor(screenHeight / 2); @@ -114,12 +115,20 @@ if (isReleaseMode) { zedBinary = "target/release/Zed"; } -execFileSync("cargo", buildArgs, { stdio: "inherit" }); +try { + execFileSync("cargo", buildArgs, { stdio: "inherit" }); +} catch (e) { + process.exit(0); +} + setTimeout(() => { for (let i = 0; i < instanceCount; i++) { const row = Math.floor(i / columns); const column = i % columns; - const position = [column * instanceWidth, row * instanceHeight].join(","); + const position = [ + column * instanceWidth, + row * instanceHeight + titleBarHeight, + ].join(","); const size = [instanceWidth, instanceHeight].join(","); spawn(zedBinary, i == 0 ? args : [], { From 4b92a15ab39007ce5278b7d530593fcbfe2efa66 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Jan 2024 13:47:43 -0800 Subject: [PATCH 333/334] Restore the colored background for collaborators that you're following --- crates/collab_ui/src/collab_panel.rs | 7 +- crates/collab_ui/src/collab_titlebar_item.rs | 134 +++++++++++-------- crates/collab_ui/src/face_pile.rs | 10 +- 3 files changed, 85 insertions(+), 66 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index d6de5135711b7e56854eaa541afc6b23a3020544..4bfb5fe3ae9988f0ad8a34769d803fa18dcbb8dd 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -17,9 +17,8 @@ use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, InteractiveElement, IntoElement, ListOffset, ListState, - Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, - SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, - WeakView, WhiteSpace, + Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, SharedString, Styled, + Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, WhiteSpace, }; use menu::{Cancel, Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; @@ -2296,7 +2295,7 @@ impl CollabPanel { h_flex() .id(channel_id as usize) .child(Label::new(channel.name.clone())) - .children(face_pile.map(|face_pile| face_pile.render(cx))), + .children(face_pile.map(|face_pile| face_pile.render().p_1())), ), ) .child( diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index ca988a9b1ad27556a988b020dad376e970838af1..e864c4c54add0ff4235928983c0fd8b3c801b071 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -57,6 +57,7 @@ impl Render for CollabTitlebarItem { let current_user = self.user_store.read(cx).current_user(); let client = self.client.clone(); let project_id = self.project.read(cx).remote_id(); + let workspace = self.workspace.upgrade(); h_flex() .id("titlebar") @@ -100,6 +101,7 @@ impl Render for CollabTitlebarItem { true, room.is_speaking(), room.is_muted(), + None, &room, project_id, ¤t_user, @@ -113,6 +115,12 @@ impl Render for CollabTitlebarItem { })) .children( remote_participants.iter().filter_map(|collaborator| { + let player_color = player_colors + .color_for_participant(collaborator.participant_index.0); + let is_following = workspace + .as_ref()? + .read(cx) + .is_being_followed(collaborator.peer_id); let is_present = project_id.map_or(false, |project_id| { collaborator.location == ParticipantLocation::SharedProject { project_id } @@ -124,6 +132,7 @@ impl Render for CollabTitlebarItem { is_present, collaborator.speaking, collaborator.muted, + is_following.then_some(player_color.selection), &room, project_id, ¤t_user, @@ -134,13 +143,7 @@ impl Render for CollabTitlebarItem { v_flex() .id(("collaborator", collaborator.user.id)) .child(face_pile) - .child(render_color_ribbon( - player_colors - .color_for_participant( - collaborator.participant_index.0, - ) - .cursor, - )) + .child(render_color_ribbon(player_color.cursor)) .cursor_pointer() .on_click({ let peer_id = collaborator.peer_id; @@ -468,11 +471,12 @@ impl CollabTitlebarItem { is_present: bool, is_speaking: bool, is_muted: bool, + leader_selection_color: Option, room: &Room, project_id: Option, current_user: &Arc, cx: &ViewContext, - ) -> Option { + ) -> Option
{ if room.role_for_user(user.id) == Some(proto::ChannelRole::Guest) { return None; } @@ -481,56 +485,72 @@ impl CollabTitlebarItem { let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id)); let extra_count = followers.len().saturating_sub(FACEPILE_LIMIT); - let pile = FacePile::default() - .child( - Avatar::new(user.avatar_uri.clone()) - .grayscale(!is_present) - .border_color(if is_speaking { - cx.theme().status().info - } else { - // We draw the border in a transparent color rather to avoid - // the layout shift that would come with adding/removing the border. - gpui::transparent_black() - }) - .when(is_muted, |avatar| { - avatar.indicator( - AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted).tooltip({ - let github_login = user.github_login.clone(); - move |cx| Tooltip::text(format!("{} is muted", github_login), cx) - }), + Some( + div() + .m_0p5() + .p_0p5() + // When the collaborator is not followed, still draw this wrapper div, but leave + // it transparent, so that it does not shift the layout when following. + .when_some(leader_selection_color, |div, color| { + div.rounded_md().bg(color) + }) + .child( + FacePile::default() + .child( + Avatar::new(user.avatar_uri.clone()) + .grayscale(!is_present) + .border_color(if is_speaking { + cx.theme().status().info + } else { + // We draw the border in a transparent color rather to avoid + // the layout shift that would come with adding/removing the border. + gpui::transparent_black() + }) + .when(is_muted, |avatar| { + avatar.indicator( + AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted) + .tooltip({ + let github_login = user.github_login.clone(); + move |cx| { + Tooltip::text( + format!("{} is muted", github_login), + cx, + ) + } + }), + ) + }), ) - }), - ) - .children( - followers - .iter() - .take(FACEPILE_LIMIT) - .filter_map(|follower_peer_id| { - let follower = room - .remote_participants() - .values() - .find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user)) - .or_else(|| { - (self.client.peer_id() == Some(*follower_peer_id)) - .then_some(current_user) - })? - .clone(); - - Some(Avatar::new(follower.avatar_uri.clone())) - }), - ) - .children(if extra_count > 0 { - Some( - div() - .ml_1() - .child(Label::new(format!("+{extra_count}"))) - .into_any_element(), - ) - } else { - None - }); - - Some(pile) + .children(followers.iter().take(FACEPILE_LIMIT).filter_map( + |follower_peer_id| { + let follower = room + .remote_participants() + .values() + .find_map(|p| { + (p.peer_id == *follower_peer_id).then_some(&p.user) + }) + .or_else(|| { + (self.client.peer_id() == Some(*follower_peer_id)) + .then_some(current_user) + })? + .clone(); + + Some(Avatar::new(follower.avatar_uri.clone())) + }, + )) + .children(if extra_count > 0 { + Some( + div() + .ml_1() + .child(Label::new(format!("+{extra_count}"))) + .into_any_element(), + ) + } else { + None + }) + .render(), + ), + ) } fn window_activation_changed(&mut self, cx: &mut ViewContext) { diff --git a/crates/collab_ui/src/face_pile.rs b/crates/collab_ui/src/face_pile.rs index fb6c59cc8079073acbb6b481e214b54619f9eea6..985c1944f465c9683cba94ddf4a5b88d974a7c96 100644 --- a/crates/collab_ui/src/face_pile.rs +++ b/crates/collab_ui/src/face_pile.rs @@ -1,13 +1,13 @@ -use gpui::{div, AnyElement, IntoElement, ParentElement, RenderOnce, Styled, WindowContext}; +use gpui::{div, AnyElement, Div, IntoElement, ParentElement, Styled}; use smallvec::SmallVec; -#[derive(Default, IntoElement)] +#[derive(Default)] pub struct FacePile { pub faces: SmallVec<[AnyElement; 2]>, } -impl RenderOnce for FacePile { - fn render(self, _: &mut WindowContext) -> impl IntoElement { +impl FacePile { + pub fn render(self) -> Div { let player_count = self.faces.len(); let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| { let isnt_last = ix < player_count - 1; @@ -17,7 +17,7 @@ impl RenderOnce for FacePile { .when(isnt_last, |div| div.neg_mr_1()) .child(player) }); - div().p_1().flex().items_center().children(player_list) + div().flex().items_center().children(player_list) } } From 93a567adfbdf552316f5715649732f2e45db5c9c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 18 Jan 2024 17:38:28 -0500 Subject: [PATCH 334/334] Add Git `.mailmap` file (#4138) This PR adds a Git [`.mailmap`](https://git-scm.com/docs/gitmailmap) file to canonicalize committer names/emails. Release Notes: - N/A --- .mailmap | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000000000000000000000000000000000..1064df3b5033f0262c1627bbe37ee69adff6df9d --- /dev/null +++ b/.mailmap @@ -0,0 +1,39 @@ +# Canonical author names and emails. +# +# Use this to provide a canonical name and email for an author when their +# name is not always written the same way and/or they have commits authored +# under different email addresses. +# +# Reference: https://git-scm.com/docs/gitmailmap + +# Keep these entries sorted alphabetically. +# In Zed: `editor: sort lines case sensitive` + +Antonio Scandurra +Antonio Scandurra +Joseph T. Lyons +Joseph T. Lyons +Julia +Julia <30666851+ForLoveOfCats@users.noreply.github.com> +Kaylee Simmons +Kaylee Simmons +Kaylee Simmons +Kaylee Simmons +Kirill Bulatov +Kirill Bulatov +Kyle Caverly +Kyle Caverly +Marshall Bowers +Marshall Bowers +Max Brunsfeld +Max Brunsfeld +Mikayla Maki +Mikayla Maki +Mikayla Maki +Nate Butler +Nate Butler +Nathan Sobo +Nathan Sobo +Nathan Sobo +Piotr Osiewicz +Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>