merged with main

KCaverly created

Change summary

Cargo.lock                                    |  29 +--
Cargo.toml                                    |   3 
README.md                                     |   5 
crates/fs/src/repository.rs                   |   9 +
crates/recent_projects/src/recent_projects.rs |   2 
crates/theme/src/theme.rs                     |   2 
crates/vcs_menu/src/lib.rs                    | 148 +++++++++++++++-----
styles/src/style_tree/picker.ts               |  42 ++++-
styles/src/style_tree/titlebar.ts             |   3 
9 files changed, 172 insertions(+), 71 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -992,7 +992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
 dependencies = [
  "memchr",
- "regex-automata 0.3.2",
+ "regex-automata 0.3.3",
  "serde",
 ]
 
@@ -1976,7 +1976,6 @@ checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc"
 dependencies = [
  "cc",
  "libc",
- "libnghttp2-sys",
  "libz-sys",
  "openssl-sys",
  "pkg-config",
@@ -2942,11 +2941,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
 [[package]]
 name = "globset"
-version = "0.4.10"
+version = "0.4.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
+checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df"
 dependencies = [
- "aho-corasick 0.7.20",
+ "aho-corasick 1.0.2",
  "bstr",
  "fnv",
  "log",
@@ -3881,16 +3880,6 @@ version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
 
-[[package]]
-name = "libnghttp2-sys"
-version = "0.1.7+1.45.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f"
-dependencies = [
- "cc",
- "libc",
-]
-
 [[package]]
 name = "libsqlite3-sys"
 version = "0.24.2"
@@ -5733,7 +5722,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
 dependencies = [
  "aho-corasick 1.0.2",
  "memchr",
- "regex-automata 0.3.2",
+ "regex-automata 0.3.3",
  "regex-syntax 0.7.4",
 ]
 
@@ -5748,9 +5737,9 @@ dependencies = [
 
 [[package]]
 name = "regex-automata"
-version = "0.3.2"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
+checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
 dependencies = [
  "aho-corasick 1.0.2",
  "memchr",
@@ -6542,9 +6531,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.100"
+version = "1.0.102"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
+checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed"
 dependencies = [
  "indexmap 2.0.0",
  "itoa 1.0.8",

Cargo.toml 🔗

@@ -83,7 +83,8 @@ env_logger = { version = "0.9" }
 futures = { version = "0.3" }
 globset = { version = "0.4" }
 indoc = "1"
-isahc = "1.7.2"
+# We explicitly disable a http2 support in isahc.
+isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
 lazy_static = { version = "1.4.0" }
 log = { version = "0.4.16", features = ["kv_unstable_serde"] }
 ordered-float = { version = "2.1.1" }

README.md 🔗

@@ -23,15 +23,16 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
     git clone https://github.com/zed-industries/zed.dev
     ```
 
-* Initialize submodules
+* Return to Zed project directory and Initialize submodules
 
     ```
+    cd zed
     git submodule update --init --recursive
     ```
 
 * Set up a local `zed` database and seed it with some initial users:
 
-    Create a personal GitHub token to run `script/bootstrap` once successfully: the token needs to have an access to private repositories for the script to work (`repo` OAuth scope).
+    [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.
 
     ```

crates/fs/src/repository.rs 🔗

@@ -39,6 +39,9 @@ pub trait GitRepository: Send {
     fn change_branch(&self, _: &str) -> Result<()> {
         Ok(())
     }
+    fn create_branch(&self, _: &str) -> Result<()> {
+        Ok(())
+    }
 }
 
 impl std::fmt::Debug for dyn GitRepository {
@@ -152,6 +155,12 @@ impl GitRepository for LibGitRepository {
         )?;
         Ok(())
     }
+    fn create_branch(&self, name: &str) -> Result<()> {
+        let current_commit = self.head()?.peel_to_commit()?;
+        self.branch(name, &current_commit, false)?;
+
+        Ok(())
+    }
 }
 
 fn read_status(status: git2::Status) -> Option<GitFileStatus> {

crates/recent_projects/src/recent_projects.rs 🔗

@@ -134,7 +134,7 @@ impl PickerDelegate for RecentProjectsDelegate {
                 let combined_string = location
                     .paths()
                     .iter()
-                    .map(|path| path.to_string_lossy().to_owned())
+                    .map(|path| util::paths::compact(&path).to_string_lossy().into_owned())
                     .collect::<Vec<_>>()
                     .join("");
                 StringMatchCandidate::new(id, combined_string)

crates/theme/src/theme.rs 🔗

@@ -586,7 +586,7 @@ pub struct Picker {
     pub no_matches: ContainedLabel,
     pub item: Toggleable<Interactive<ContainedLabel>>,
     pub header: ContainedLabel,
-    pub footer: ContainedLabel,
+    pub footer: Interactive<ContainedLabel>,
 }
 
 #[derive(Clone, Debug, Deserialize, Default, JsonSchema)]

crates/vcs_menu/src/lib.rs 🔗

@@ -1,6 +1,11 @@
 use anyhow::{anyhow, bail, Result};
 use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{actions, elements::*, AppContext, MouseState, Task, ViewContext, ViewHandle};
+use gpui::{
+    actions,
+    elements::*,
+    platform::{CursorStyle, MouseButton},
+    AppContext, MouseState, Task, ViewContext, ViewHandle,
+};
 use picker::{Picker, PickerDelegate, PickerEvent};
 use std::{ops::Not, sync::Arc};
 use util::ResultExt;
@@ -24,6 +29,7 @@ pub fn build_branch_list(
             workspace,
             selected_index: 0,
             last_query: String::default(),
+            branch_name_trailoff_after: 29,
         },
         cx,
     )
@@ -46,6 +52,8 @@ fn toggle(
                             workspace,
                             selected_index: 0,
                             last_query: String::default(),
+                            /// Modal branch picker has a longer trailoff than a popover one.
+                            branch_name_trailoff_after: 70,
                         },
                         cx,
                     )
@@ -63,8 +71,18 @@ pub struct BranchListDelegate {
     workspace: ViewHandle<Workspace>,
     selected_index: usize,
     last_query: String,
+    /// Max length of branch name before we truncate it and add a trailing `...`.
+    branch_name_trailoff_after: usize,
 }
 
+impl BranchListDelegate {
+    fn display_error_toast(&self, message: String, cx: &mut ViewContext<BranchList>) {
+        const GIT_CHECKOUT_FAILURE_ID: usize = 2048;
+        self.workspace.update(cx, |model, ctx| {
+            model.show_toast(Toast::new(GIT_CHECKOUT_FAILURE_ID, message), ctx)
+        });
+    }
+}
 impl PickerDelegate for BranchListDelegate {
     fn placeholder_text(&self) -> Arc<str> {
         "Select branch...".into()
@@ -166,40 +184,39 @@ impl PickerDelegate for BranchListDelegate {
         let current_pick = self.selected_index();
         let current_pick = self.matches[current_pick].string.clone();
         cx.spawn(|picker, mut cx| async move {
-            picker.update(&mut cx, |this, cx| {
-                let project = this.delegate().workspace.read(cx).project().read(cx);
-                let mut cwd = project
-                .visible_worktrees(cx)
-                .next()
-                .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
-                .read(cx)
-                .abs_path()
-                .to_path_buf();
-                cwd.push(".git");
-                let status = project
-                    .fs()
-                    .open_repo(&cwd)
-                    .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?
-                    .lock()
-                    .change_branch(&current_pick);
-                if status.is_err() {
-                    const GIT_CHECKOUT_FAILURE_ID: usize = 2048;
-                    this.delegate().workspace.update(cx, |model, ctx| {
-                        model.show_toast(
-                            Toast::new(
-                                GIT_CHECKOUT_FAILURE_ID,
-                                format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"),
-                            ),
-                            ctx,
-                        )
-                    });
-                    status?;
-                }
-                cx.emit(PickerEvent::Dismiss);
+            picker
+                .update(&mut cx, |this, cx| {
+                    let project = this.delegate().workspace.read(cx).project().read(cx);
+                    let mut cwd = project
+                        .visible_worktrees(cx)
+                        .next()
+                        .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
+                        .read(cx)
+                        .abs_path()
+                        .to_path_buf();
+                    cwd.push(".git");
+                    let status = project
+                        .fs()
+                        .open_repo(&cwd)
+                        .ok_or_else(|| {
+                            anyhow!(
+                                "Could not open repository at path `{}`",
+                                cwd.as_os_str().to_string_lossy()
+                            )
+                        })?
+                        .lock()
+                        .change_branch(&current_pick);
+                    if status.is_err() {
+                        this.delegate().display_error_toast(format!("Failed to checkout branch '{current_pick}', check for conflicts or unstashed files"), cx);
+                        status?;
+                    }
+                    cx.emit(PickerEvent::Dismiss);
 
-                Ok::<(), anyhow::Error>(())
-            }).log_err();
-        }).detach();
+                    Ok::<(), anyhow::Error>(())
+                })
+                .log_err();
+        })
+        .detach();
     }
 
     fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
@@ -213,15 +230,15 @@ impl PickerDelegate for BranchListDelegate {
         selected: bool,
         cx: &gpui::AppContext,
     ) -> AnyElement<Picker<Self>> {
-        const DISPLAYED_MATCH_LEN: usize = 29;
         let theme = &theme::current(cx);
         let hit = &self.matches[ix];
-        let shortened_branch_name = util::truncate_and_trailoff(&hit.string, DISPLAYED_MATCH_LEN);
+        let shortened_branch_name =
+            util::truncate_and_trailoff(&hit.string, self.branch_name_trailoff_after);
         let highlights = hit
             .positions
             .iter()
             .copied()
-            .filter(|index| index < &DISPLAYED_MATCH_LEN)
+            .filter(|index| index < &self.branch_name_trailoff_after)
             .collect();
         let style = theme.picker.item.in_state(selected).style_for(mouse_state);
         Flex::row()
@@ -265,4 +282,61 @@ impl PickerDelegate for BranchListDelegate {
         };
         Some(label.into_any())
     }
+    fn render_footer(
+        &self,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> Option<AnyElement<Picker<Self>>> {
+        if !self.last_query.is_empty() {
+            let theme = &theme::current(cx);
+            let style = theme.picker.footer.clone();
+            enum BranchCreateButton {}
+            Some(
+                Flex::row().with_child(MouseEventHandler::<BranchCreateButton, _>::new(0, cx, |state, _| {
+                    let style = style.style_for(state);
+                    Label::new("Create branch", style.label.clone())
+                        .contained()
+                        .with_style(style.container)
+                })
+                .with_cursor_style(CursorStyle::PointingHand)
+                .on_down(MouseButton::Left, |_, _, cx| {
+                    cx.spawn(|picker, mut cx| async move {
+                        picker.update(&mut cx, |this, cx| {
+                            let project = this.delegate().workspace.read(cx).project().read(cx);
+                            let current_pick = &this.delegate().last_query;
+                            let mut cwd = project
+                            .visible_worktrees(cx)
+                            .next()
+                            .ok_or_else(|| anyhow!("There are no visisible worktrees."))?
+                            .read(cx)
+                            .abs_path()
+                            .to_path_buf();
+                            cwd.push(".git");
+                            let repo = project
+                                .fs()
+                                .open_repo(&cwd)
+                                .ok_or_else(|| anyhow!("Could not open repository at path `{}`", cwd.as_os_str().to_string_lossy()))?;
+                            let repo = repo
+                                .lock();
+                            let status = repo
+                                .create_branch(&current_pick);
+                            if status.is_err() {
+                                this.delegate().display_error_toast(format!("Failed to create branch '{current_pick}', check for conflicts or unstashed files"), cx);
+                                status?;
+                            }
+                            let status = repo.change_branch(&current_pick);
+                            if status.is_err() {
+                                this.delegate().display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx);
+                                status?;
+                            }
+                            cx.emit(PickerEvent::Dismiss);
+                            Ok::<(), anyhow::Error>(())
+                })
+                    }).detach();
+                })).aligned().right()
+                .into_any(),
+            )
+        } else {
+            None
+        }
+    }
 }

styles/src/style_tree/picker.ts 🔗

@@ -119,14 +119,40 @@ export default function picker(): any {
                 right: 8,
             },
         },
-        footer: {
-            text: text(theme.lowest, "sans", "variant", { size: "xs" }),
-            margin: {
-                top: 1,
-                left: 8,
-                right: 8,
+        footer: interactive({
+            base: {
+                text: text(theme.lowest, "sans", "base", { size: "xs" }),
+                padding: {
+                    bottom: 4,
+                    left: 12,
+                    right: 12,
+                    top: 4,
+                },
+                margin: {
+                    top: 1,
+                    left: 4,
+                    right: 4,
+                },
+                corner_radius: 8,
+                background: with_opacity(
+                    background(theme.lowest, "active"),
+                    0.5
+                ),
             },
-
-        }
+            state: {
+                hovered: {
+                    background: with_opacity(
+                        background(theme.lowest, "hovered"),
+                        0.5
+                    ),
+                },
+                clicked: {
+                    background: with_opacity(
+                        background(theme.lowest, "pressed"),
+                        0.5
+                    ),
+                },
+            }
+        }),
     }
 }

styles/src/style_tree/titlebar.ts 🔗

@@ -84,7 +84,7 @@ function user_menu() {
                 base: {
                     corner_radius: 6,
                     height: button_height,
-                    width: online ? 37 : 24,
+                    width: 20,
                     padding: {
                         top: 2,
                         bottom: 2,
@@ -153,6 +153,7 @@ function user_menu() {
             },
         }
     }
+
     return {
         user_menu_button_online: build_button({ online: true }),
         user_menu_button_offline: build_button({ online: false }),