diff --git a/Cargo.lock b/Cargo.lock index fe6a104ef2..a03291f253 100644 --- a/Cargo.lock +++ b/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", diff --git a/Cargo.toml b/Cargo.toml index 983f91081a..529f297f70 100644 --- a/Cargo.toml +++ b/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" } diff --git a/README.md b/README.md index 6908cebf24..375c4a7ed6 100644 --- a/README.md +++ b/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. ``` diff --git a/crates/fs/src/repository.rs b/crates/fs/src/repository.rs index 0e5fd8343f..ed9aa85a89 100644 --- a/crates/fs/src/repository.rs +++ b/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, ¤t_commit, false)?; + + Ok(()) + } } fn read_status(status: git2::Status) -> Option { diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 4ba6103167..cd512f1e57 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/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::>() .join(""); StringMatchCandidate::new(id, combined_string) diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index fae7c470e3..4e8ece1c8f 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -586,7 +586,7 @@ pub struct Picker { pub no_matches: ContainedLabel, pub item: Toggleable>, pub header: ContainedLabel, - pub footer: ContainedLabel, + pub footer: Interactive, } #[derive(Clone, Debug, Deserialize, Default, JsonSchema)] diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index b5b1036b36..d4d0a3d187 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/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, 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) { + 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 { "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(¤t_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(¤t_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>) { @@ -213,15 +230,15 @@ impl PickerDelegate for BranchListDelegate { selected: bool, cx: &gpui::AppContext, ) -> AnyElement> { - 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>, + ) -> Option>> { + 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::::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(¤t_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(¤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); + status?; + } + cx.emit(PickerEvent::Dismiss); + Ok::<(), anyhow::Error>(()) + }) + }).detach(); + })).aligned().right() + .into_any(), + ) + } else { + None + } + } } diff --git a/styles/src/style_tree/picker.ts b/styles/src/style_tree/picker.ts index bbd664397f..28ae854787 100644 --- a/styles/src/style_tree/picker.ts +++ b/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 + ), + }, + } + }), } } diff --git a/styles/src/style_tree/titlebar.ts b/styles/src/style_tree/titlebar.ts index 60894b08f6..177a8c5bd8 100644 --- a/styles/src/style_tree/titlebar.ts +++ b/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 }),