Compare commits

...

15 Commits

Author SHA1 Message Date
Zed Bot
a55eb87bdb Bump to 0.214.2 for @Veykril 2025-11-24 12:56:39 +00:00
zed-zippy[bot]
325c857409 proto: Fix cloned errors losing all context (#43393) (cherry-pick to preview) (#43395)
Cherry-pick of #43393 to preview

----
Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-11-24 12:42:49 +00:00
zed-zippy[bot]
425901cf73 crashes: Print panic message to logs (#43159) (cherry-pick to preview) (#43258)
Cherry-pick of #43159 to preview

----
Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-11-24 11:21:39 +00:00
zed-zippy[bot]
f0ef48562b agent: Fix utf8 panic in outline (#43141) (cherry-pick to preview) (#43385)
Cherry-pick of #43141 to preview

----
Fixes ZED-3F3

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-11-24 09:52:15 +00:00
zed-zippy[bot]
81fcb11073 askpass: Fix double command ampersand in powershell script (#43289) (cherry-pick to preview) (#43290)
Cherry-pick of #43289 to preview

----
Fixes https://github.com/zed-industries/zed/issues/42618 /
https://github.com/zed-industries/zed/issues/43109

Release Notes:

- N/A

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-11-22 00:56:37 +00:00
zed-zippy[bot]
de2c00b9f0 git: Handle git pre-commit hooks separately (#43285) (cherry-pick to preview) (#43286)
Cherry-pick of #43285 to preview

----
We now run git pre-commit hooks before we commit. This ensures we don't
run into timeout issues with askpass delegate and report invalid error
to the user.

Closes #43157

Release Notes:

- Fixed long running pre-commit hooks causing committing from Zed to
fail.

Co-authored-by: Cole Miller <cole@zed.dev>

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
Co-authored-by: Cole Miller <cole@zed.dev>
2025-11-21 23:43:51 +00:00
Zed Bot
a9eab53619 Bump to 0.214.1 for @Veykril 2025-11-21 18:11:54 +00:00
zed-zippy[bot]
919330a1ff Disable keychain timeout in bundle-mac (#43204) (cherry-pick to preview) (#43259)
Cherry-pick of #43204 to preview

----
Attempt to reduce the number of times bundle-mac fails to notorize by
disabling
keychain's auto-lock timeout

Release Notes:

- N/A

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
2025-11-21 16:57:48 +00:00
zed-zippy[bot]
680c66728a Revert "gpui: Convert macOS clipboard file URLs to paths for paste" (#43254) (cherry-pick to stable) (#43256)
Cherry-pick of #43254 to stable

----
Reverts zed-industries/zed#36848

Turns out this broke copying a screenshot from apps like CleanShot X and
then pasting it over. We should land this again after taking a look at
those cases. Pasting screenshots from the native macOS screenshot
functionality works though.

cc @seantimm 

Release Notes:

- Fixed issue where copying a screenshot from apps like CleanShot X into
Agent Panel didn't work as expected.

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
2025-11-21 16:23:37 +00:00
zed-zippy[bot]
c210465e5c Revert "util: Check whether discovered powershell is actually executable" (#43247) (cherry-pick to stable) (#43248)
Cherry-pick of #43247 to stable

----
Reverts zed-industries/zed#43044
Closes https://github.com/zed-industries/zed/issues/43224

This slows down startup on windows significantly

Release Notes:

- Fixed slow startup on Windows

Co-authored-by: Lukas Wirth <lukas@zed.dev>
2025-11-21 14:58:27 +00:00
zed-zippy[bot]
d22d86bf03 Fix panic in the git panel when toggling sort_by_path (#43074) (cherry-pick to stable) (#43128)
Cherry-pick of #43074 to stable

----
We call `entry_by_path` on the `bulk_staging` anchor entry at the
beginning of `update_visible_entries`, but in the codepath where
`sort_by_path` is toggled on or off, we clear entries without clearing
`bulk_staging` or counts, causing that `entry_by_path` to do an out of
bounds index. Fixed by clearing `bulk_staging` as well.

Release Notes:

- N/A

Co-authored-by: Cole Miller <cole@zed.dev>
2025-11-20 07:04:25 +00:00
zed-zippy[bot]
97009296ea git: Clear pending ops for remote repos (#43098) (cherry-pick to preview) (#43100)
Cherry-pick of #43098 to preview

----
Release Notes:

- N/A

Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>
2025-11-19 21:59:20 +00:00
John Tur
9c45875c18 Fix Windows bundling (#43083)
The updated package from
https://github.com/zed-industries/zed/pull/43066 changed the paths of
these files in the nupkg.

Release Notes:

- N/A
2025-11-19 13:15:01 -05:00
Joseph T. Lyons
52f494d330 Revert "editor: Fix prepaint recursion when updating stale sizes (#42896)"
This reverts commit 79be5cbfe2.
2025-11-19 10:51:37 -05:00
Joseph T. Lyons
49eaaff379 v0.214.x preview 2025-11-19 10:41:44 -05:00
24 changed files with 219 additions and 248 deletions

2
Cargo.lock generated
View File

@@ -21233,7 +21233,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.214.0"
version = "0.214.2"
dependencies = [
"acp_tools",
"activity_indicator",

View File

@@ -48,7 +48,7 @@ pub async fn get_buffer_content_or_outline(
if outline_items.is_empty() {
let text = buffer.read_with(cx, |buffer, _| {
let snapshot = buffer.snapshot();
let len = snapshot.len().min(1024);
let len = snapshot.len().min(snapshot.as_rope().floor_char_boundary(1024));
let content = snapshot.text_for_range(0..len).collect::<String>();
if let Some(path) = path {
format!("# First 1KB of {path} (file too large to show full content, and no outline available)\n\n{content}")
@@ -178,7 +178,7 @@ mod tests {
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;
let content = "A".repeat(100 * 1024); // 100KB
let content = "".repeat(100 * 1024); // 100KB
let content_len = content.len();
let buffer = project
.update(cx, |project, cx| project.create_buffer(true, cx))
@@ -194,7 +194,7 @@ mod tests {
// Should contain some of the actual file content
assert!(
result.text.contains("AAAAAAAAAA"),
result.text.contains("⚡⚡⚡⚡⚡⚡⚡"),
"Result did not contain content subset"
);

View File

@@ -250,7 +250,6 @@ impl PasswordProxy {
.await
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
make_file_executable(&askpass_script_path).await?;
// todo(shell): There might be no powershell on the system
#[cfg(target_os = "windows")]
let askpass_helper = format!(
"powershell.exe -ExecutionPolicy Bypass -File {}",
@@ -375,7 +374,7 @@ fn generate_askpass_script(
Ok(format!(
r#"
$ErrorActionPreference = 'Stop';
($args -join [char]0) | & {askpass_program} --askpass={askpass_socket} 2> $null
($args -join [char]0) | {askpass_program} --askpass={askpass_socket} 2> $null
"#,
))
}

View File

@@ -453,6 +453,7 @@ impl Server {
.add_request_handler(forward_mutating_project_request::<proto::StashPop>)
.add_request_handler(forward_mutating_project_request::<proto::StashDrop>)
.add_request_handler(forward_mutating_project_request::<proto::Commit>)
.add_request_handler(forward_mutating_project_request::<proto::RunGitHook>)
.add_request_handler(forward_mutating_project_request::<proto::GitInit>)
.add_request_handler(forward_read_only_project_request::<proto::GetRemotes>)
.add_request_handler(forward_read_only_project_request::<proto::GitShow>)

View File

@@ -51,11 +51,13 @@ pub async fn init(crash_init: InitCrashHandler) {
unsafe { env::set_var("RUST_BACKTRACE", "1") };
old_hook(info);
// prevent the macOS crash dialog from popping up
std::process::exit(1);
if cfg!(target_os = "macos") {
std::process::exit(1);
}
}));
return;
}
(Some(true), _) | (None, _) => {
_ => {
panic::set_hook(Box::new(panic_hook));
}
}
@@ -300,11 +302,18 @@ pub fn panic_hook(info: &PanicHookInfo) {
.map(|loc| format!("{}:{}", loc.file(), loc.line()))
.unwrap_or_default();
let current_thread = std::thread::current();
let thread_name = current_thread.name().unwrap_or("<unnamed>");
// wait 500ms for the crash handler process to start up
// if it's still not there just write panic info and no minidump
let retry_frequency = Duration::from_millis(100);
for _ in 0..5 {
if let Some(client) = CRASH_HANDLER.get() {
let location = info
.location()
.map_or_else(|| "<unknown>".to_owned(), |location| location.to_string());
log::error!("thread '{thread_name}' panicked at {location}:\n{message}...");
client
.send_message(
2,

View File

@@ -74,7 +74,6 @@ use smallvec::{SmallVec, smallvec};
use std::{
any::TypeId,
borrow::Cow,
cell::Cell,
cmp::{self, Ordering},
fmt::{self, Write},
iter, mem,
@@ -186,13 +185,6 @@ impl SelectionLayout {
}
}
#[derive(Default)]
struct RenderBlocksOutput {
blocks: Vec<BlockLayout>,
row_block_types: HashMap<DisplayRow, bool>,
resized_blocks: Option<HashMap<CustomBlockId, u32>>,
}
pub struct EditorElement {
editor: Entity<Editor>,
style: EditorStyle,
@@ -3675,7 +3667,6 @@ impl EditorElement {
latest_selection_anchors: &HashMap<BufferId, Anchor>,
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
sticky_header_excerpt_id: Option<ExcerptId>,
block_resize_offset: &mut i32,
window: &mut Window,
cx: &mut App,
) -> Option<(AnyElement, Size<Pixels>, DisplayRow, Pixels)> {
@@ -3829,10 +3820,7 @@ impl EditorElement {
};
let mut element_height_in_lines = ((final_size.height / line_height).ceil() as u32).max(1);
let effective_row_start = block_row_start.0 as i32 + *block_resize_offset;
debug_assert!(effective_row_start >= 0);
let mut row = DisplayRow(effective_row_start.max(0) as u32);
let mut row = block_row_start;
let mut x_offset = px(0.);
let mut is_block = true;
@@ -3862,7 +3850,6 @@ impl EditorElement {
}
};
if element_height_in_lines != block.height() {
*block_resize_offset += element_height_in_lines as i32 - block.height() as i32;
resized_blocks.insert(custom_block_id, element_height_in_lines);
}
}
@@ -4267,7 +4254,7 @@ impl EditorElement {
sticky_header_excerpt_id: Option<ExcerptId>,
window: &mut Window,
cx: &mut App,
) -> RenderBlocksOutput {
) -> Result<(Vec<BlockLayout>, HashMap<DisplayRow, bool>), HashMap<CustomBlockId, u32>> {
let (fixed_blocks, non_fixed_blocks) = snapshot
.blocks_in_range(rows.clone())
.partition::<Vec<_>, _>(|(_, block)| block.style() == BlockStyle::Fixed);
@@ -4279,7 +4266,6 @@ impl EditorElement {
let mut blocks = Vec::new();
let mut resized_blocks = HashMap::default();
let mut row_block_types = HashMap::default();
let mut block_resize_offset: i32 = 0;
for (row, block) in fixed_blocks {
let block_id = block.id();
@@ -4310,7 +4296,6 @@ impl EditorElement {
latest_selection_anchors,
is_row_soft_wrapped,
sticky_header_excerpt_id,
&mut block_resize_offset,
window,
cx,
) {
@@ -4369,7 +4354,6 @@ impl EditorElement {
latest_selection_anchors,
is_row_soft_wrapped,
sticky_header_excerpt_id,
&mut block_resize_offset,
window,
cx,
) {
@@ -4426,7 +4410,6 @@ impl EditorElement {
latest_selection_anchors,
is_row_soft_wrapped,
sticky_header_excerpt_id,
&mut block_resize_offset,
window,
cx,
) {
@@ -4446,12 +4429,9 @@ impl EditorElement {
if resized_blocks.is_empty() {
*scroll_width =
(*scroll_width).max(fixed_block_max_width - editor_margins.gutter.width);
}
RenderBlocksOutput {
blocks,
row_block_types,
resized_blocks: (!resized_blocks.is_empty()).then_some(resized_blocks),
Ok((blocks, row_block_types))
} else {
Err(resized_blocks)
}
}
@@ -8792,48 +8772,8 @@ impl EditorElement {
}
}
#[derive(Default)]
pub struct EditorRequestLayoutState {
// We use prepaint depth to limit the number of times prepaint is
// called recursively. We need this so that we can update stale
// data for e.g. block heights in block map.
prepaint_depth: Rc<Cell<usize>>,
}
impl EditorRequestLayoutState {
// In ideal conditions we only need one more subsequent prepaint call for resize to take effect.
// i.e. MAX_PREPAINT_DEPTH = 2, but since moving blocks inline (place_near), more lines from
// below get exposed, and we end up querying blocks for those lines too in subsequent renders.
// Setting MAX_PREPAINT_DEPTH = 3, passes all tests. Just to be on the safe side we set it to 5, so
// that subsequent shrinking does not lead to incorrect block placing.
const MAX_PREPAINT_DEPTH: usize = 5;
fn increment_prepaint_depth(&self) -> EditorPrepaintGuard {
let depth = self.prepaint_depth.get();
self.prepaint_depth.set(depth + 1);
EditorPrepaintGuard {
prepaint_depth: self.prepaint_depth.clone(),
}
}
fn can_prepaint(&self) -> bool {
self.prepaint_depth.get() < Self::MAX_PREPAINT_DEPTH
}
}
struct EditorPrepaintGuard {
prepaint_depth: Rc<Cell<usize>>,
}
impl Drop for EditorPrepaintGuard {
fn drop(&mut self) {
let depth = self.prepaint_depth.get();
self.prepaint_depth.set(depth.saturating_sub(1));
}
}
impl Element for EditorElement {
type RequestLayoutState = EditorRequestLayoutState;
type RequestLayoutState = ();
type PrepaintState = EditorLayout;
fn id(&self) -> Option<ElementId> {
@@ -8850,7 +8790,7 @@ impl Element for EditorElement {
_inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
) -> (gpui::LayoutId, ()) {
let rem_size = self.rem_size(cx);
window.with_rem_size(rem_size, |window| {
self.editor.update(cx, |editor, cx| {
@@ -8917,7 +8857,7 @@ impl Element for EditorElement {
}
};
(layout_id, EditorRequestLayoutState::default())
(layout_id, ())
})
})
}
@@ -8927,11 +8867,10 @@ impl Element for EditorElement {
_: Option<&GlobalElementId>,
_inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
_: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState {
let _prepaint_depth_guard = request_layout.increment_prepaint_depth();
let text_style = TextStyleRefinement {
font_size: Some(self.style.text.font_size),
line_height: Some(self.style.text.line_height),
@@ -9455,20 +9394,7 @@ impl Element for EditorElement {
// If the fold widths have changed, we need to prepaint
// the element again to account for any changes in
// wrapping.
if request_layout.can_prepaint() {
return self.prepaint(
None,
_inspector_id,
bounds,
request_layout,
window,
cx,
);
} else {
debug_panic!(
"skipping recursive prepaint at max depth. renderer widths may be stale."
);
}
return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
}
let longest_line_blame_width = self
@@ -9555,35 +9481,20 @@ impl Element for EditorElement {
)
})
})
.unwrap_or_default();
let RenderBlocksOutput {
mut blocks,
row_block_types,
resized_blocks,
} = blocks;
if let Some(resized_blocks) = resized_blocks {
self.editor.update(cx, |editor, cx| {
editor.resize_blocks(
resized_blocks,
autoscroll_request.map(|(autoscroll, _)| autoscroll),
cx,
)
});
if request_layout.can_prepaint() {
return self.prepaint(
None,
_inspector_id,
bounds,
request_layout,
window,
cx,
);
} else {
debug_panic!(
"skipping recursive prepaint at max depth. block layout may be stale."
);
.unwrap_or_else(|| Ok((Vec::default(), HashMap::default())));
let (mut blocks, row_block_types) = match blocks {
Ok(blocks) => blocks,
Err(resized_blocks) => {
self.editor.update(cx, |editor, cx| {
editor.resize_blocks(
resized_blocks,
autoscroll_request.map(|(autoscroll, _)| autoscroll),
cx,
)
});
return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
}
}
};
let sticky_buffer_header = sticky_header_excerpt.map(|sticky_header_excerpt| {
window.with_element_namespace("blocks", |window| {

View File

@@ -3,7 +3,7 @@ use anyhow::{Context as _, Result, bail};
use collections::{HashMap, HashSet};
use futures::future::{self, BoxFuture, join_all};
use git::{
Oid,
Oid, RunHook,
blame::Blame,
repository::{
AskPassDelegate, Branch, CommitDetails, CommitOptions, FetchOptions, GitRepository,
@@ -532,6 +532,14 @@ impl GitRepository for FakeGitRepository {
unimplemented!()
}
fn run_hook(
&self,
_hook: RunHook,
_env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
unimplemented!()
}
fn push(
&self,
_branch: String,

View File

@@ -225,3 +225,28 @@ impl From<Oid> for usize {
u64::from_ne_bytes(u64_bytes) as usize
}
}
#[repr(i32)]
#[derive(Copy, Clone, Debug)]
pub enum RunHook {
PreCommit,
}
impl RunHook {
pub fn as_str(&self) -> &str {
match self {
Self::PreCommit => "pre-commit",
}
}
pub fn to_proto(&self) -> i32 {
*self as i32
}
pub fn from_proto(value: i32) -> Option<Self> {
match value {
0 => Some(Self::PreCommit),
_ => None,
}
}
}

View File

@@ -1,7 +1,7 @@
use crate::commit::parse_git_diff_name_status;
use crate::stash::GitStash;
use crate::status::{DiffTreeType, GitStatus, StatusCode, TreeDiff};
use crate::{Oid, SHORT_SHA_LENGTH};
use crate::{Oid, RunHook, SHORT_SHA_LENGTH};
use anyhow::{Context as _, Result, anyhow, bail};
use collections::HashMap;
use futures::future::BoxFuture;
@@ -485,6 +485,12 @@ pub trait GitRepository: Send + Sync {
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>>;
fn run_hook(
&self,
hook: RunHook,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>>;
fn commit(
&self,
message: SharedString,
@@ -1643,6 +1649,7 @@ impl GitRepository for RealGitRepository {
.args(["commit", "--quiet", "-m"])
.arg(&message.to_string())
.arg("--cleanup=strip")
.arg("--no-verify")
.stdout(smol::process::Stdio::piped())
.stderr(smol::process::Stdio::piped());
@@ -2037,6 +2044,26 @@ impl GitRepository for RealGitRepository {
})
.boxed()
}
fn run_hook(
&self,
hook: RunHook,
env: Arc<HashMap<String, String>>,
) -> BoxFuture<'_, Result<()>> {
let working_directory = self.working_directory();
let git_binary_path = self.any_git_binary_path.clone();
let executor = self.executor.clone();
self.executor
.spawn(async move {
let working_directory = working_directory?;
let git = GitBinary::new(git_binary_path, working_directory, executor)
.envs(HashMap::clone(&env));
git.run(&["hook", "run", "--ignore-missing", hook.as_str()])
.await?;
Ok(())
})
.boxed()
}
}
fn git_status_args(path_prefixes: &[RepoPath]) -> Vec<OsString> {

View File

@@ -373,6 +373,7 @@ impl GitPanel {
let is_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path;
if is_sort_by_path != was_sort_by_path {
this.entries.clear();
this.bulk_staging.take();
this.update_visible_entries(window, cx);
}
was_sort_by_path = is_sort_by_path

View File

@@ -1135,32 +1135,7 @@ impl Platform for MacPlatform {
}
}
// Next, check for URL flavors (including file URLs). Some tools only provide a URL
// with no plain text entry.
{
// Try the modern UTType identifiers first.
let file_url_type: id = ns_string("public.file-url");
let url_type: id = ns_string("public.url");
let url_data = if msg_send![types, containsObject: file_url_type] {
pasteboard.dataForType(file_url_type)
} else if msg_send![types, containsObject: url_type] {
pasteboard.dataForType(url_type)
} else {
nil
};
if url_data != nil && !url_data.bytes().is_null() {
let bytes = slice::from_raw_parts(
url_data.bytes() as *mut u8,
url_data.length() as usize,
);
return Some(self.read_string_from_clipboard(&state, bytes));
}
}
// If it wasn't a string or URL, try the various supported image types.
// If it wasn't a string, try the various supported image types.
for format in ImageFormat::iter() {
if let Some(item) = try_clipboard_image(pasteboard, format) {
return Some(item);
@@ -1168,7 +1143,7 @@ impl Platform for MacPlatform {
}
}
// If it wasn't a string, URL, or a supported image type, give up.
// If it wasn't a string or a supported image type, give up.
None
}
@@ -1743,40 +1718,6 @@ mod tests {
);
}
#[test]
fn test_file_url_reads_as_url_string() {
let platform = build_platform();
// Create a file URL for an arbitrary test path and write it to the pasteboard.
// This path does not need to exist; we only validate URL→path conversion.
let mock_path = "/tmp/zed-clipboard-file-url-test";
unsafe {
// Build an NSURL from the file path
let url: id = msg_send![class!(NSURL), fileURLWithPath: ns_string(mock_path)];
let abs: id = msg_send![url, absoluteString];
// Encode the URL string as UTF-8 bytes
let len: usize = msg_send![abs, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
let bytes_ptr = abs.UTF8String() as *const u8;
let data = NSData::dataWithBytes_length_(nil, bytes_ptr as *const c_void, len as u64);
// Write as public.file-url to the unique pasteboard
let file_url_type: id = ns_string("public.file-url");
platform
.0
.lock()
.pasteboard
.setData_forType(data, file_url_type);
}
// Ensure the clipboard read returns the URL string, not a converted path
let expected_url = format!("file://{}", mock_path);
assert_eq!(
platform.read_from_clipboard(),
Some(ClipboardItem::new_string(expected_url))
);
}
fn build_platform() -> MacPlatform {
let platform = MacPlatform::new(false);
platform.0.lock().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };

View File

@@ -390,12 +390,10 @@ impl Platform for WindowsPlatform {
clippy::disallowed_methods,
reason = "We are restarting ourselves, using std command thus is fine"
)]
// todo(shell): There might be no powershell on the system
let restart_process =
util::command::new_std_command(util::shell::get_windows_system_shell())
.arg("-command")
.arg(script)
.spawn();
let restart_process = util::command::new_std_command("powershell.exe")
.arg("-command")
.arg(script)
.spawn();
match restart_process {
Ok(_) => self.quit(),

View File

@@ -25,7 +25,7 @@ use futures::{
stream::FuturesOrdered,
};
use git::{
BuildPermalinkParams, GitHostingProviderRegistry, Oid,
BuildPermalinkParams, GitHostingProviderRegistry, Oid, RunHook,
blame::Blame,
parse_git_remote_url,
repository::{
@@ -433,6 +433,7 @@ impl GitStore {
client.add_entity_request_handler(Self::handle_stash_apply);
client.add_entity_request_handler(Self::handle_stash_drop);
client.add_entity_request_handler(Self::handle_commit);
client.add_entity_request_handler(Self::handle_run_hook);
client.add_entity_request_handler(Self::handle_reset);
client.add_entity_request_handler(Self::handle_show);
client.add_entity_request_handler(Self::handle_load_commit_diff);
@@ -1982,6 +1983,22 @@ impl GitStore {
Ok(proto::Ack {})
}
async fn handle_run_hook(
this: Entity<Self>,
envelope: TypedEnvelope<proto::RunGitHook>,
mut cx: AsyncApp,
) -> Result<proto::Ack> {
let repository_id = RepositoryId::from_proto(envelope.payload.repository_id);
let repository_handle = Self::repository_for_request(&this, repository_id, &mut cx)?;
let hook = RunHook::from_proto(envelope.payload.hook).context("invalid hook")?;
repository_handle
.update(&mut cx, |repository_handle, cx| {
repository_handle.run_hook(hook, cx)
})?
.await??;
Ok(proto::Ack {})
}
async fn handle_commit(
this: Entity<Self>,
envelope: TypedEnvelope<proto::Commit>,
@@ -4262,19 +4279,49 @@ impl Repository {
})
}
pub fn run_hook(&mut self, hook: RunHook, _cx: &mut App) -> oneshot::Receiver<Result<()>> {
let id = self.id;
self.send_job(
Some(format!("git hook {}", hook.as_str()).into()),
move |git_repo, _cx| async move {
match git_repo {
RepositoryState::Local {
backend,
environment,
} => backend.run_hook(hook, environment.clone()).await,
RepositoryState::Remote { project_id, client } => {
client
.request(proto::RunGitHook {
project_id: project_id.0,
repository_id: id.to_proto(),
hook: hook.to_proto(),
})
.await?;
Ok(())
}
}
},
)
}
pub fn commit(
&mut self,
message: SharedString,
name_and_email: Option<(SharedString, SharedString)>,
options: CommitOptions,
askpass: AskPassDelegate,
_cx: &mut App,
cx: &mut App,
) -> oneshot::Receiver<Result<()>> {
let id = self.id;
let askpass_delegates = self.askpass_delegates.clone();
let askpass_id = util::post_inc(&mut self.latest_askpass_id);
let rx = self.run_hook(RunHook::PreCommit, cx);
self.send_job(Some("git commit".into()), move |git_repo, _cx| async move {
rx.await??;
match git_repo {
RepositoryState::Local {
backend,
@@ -4999,6 +5046,7 @@ impl Repository {
if update.is_last_update {
self.snapshot.scan_id = update.scan_id;
}
self.clear_pending_ops(cx);
Ok(())
}

View File

@@ -531,3 +531,13 @@ message GitCreateWorktree {
string directory = 4;
optional string commit = 5;
}
message RunGitHook {
enum GitHook {
PRE_COMMIT = 0;
}
uint64 project_id = 1;
uint64 repository_id = 2;
GitHook hook = 3;
}

View File

@@ -437,7 +437,9 @@ message Envelope {
OpenImageResponse open_image_response = 392;
CreateImageForPeer create_image_for_peer = 393;
ExternalExtensionAgentsUpdated external_extension_agents_updated = 394; // current max
ExternalExtensionAgentsUpdated external_extension_agents_updated = 394;
RunGitHook run_git_hook = 395; // current max
}
reserved 87 to 88;

View File

@@ -126,7 +126,7 @@ impl ErrorExt for anyhow::Error {
if let Some(rpc_error) = self.downcast_ref::<RpcError>() {
rpc_error.cloned()
} else {
anyhow::anyhow!("{self}")
anyhow::anyhow!("{self:#}")
}
}
}

View File

@@ -49,6 +49,7 @@ messages!(
(ChannelMessageUpdate, Foreground),
(CloseBuffer, Foreground),
(Commit, Background),
(RunGitHook, Background),
(CopyProjectEntry, Foreground),
(CreateBufferForPeer, Foreground),
(CreateImageForPeer, Foreground),
@@ -349,6 +350,7 @@ request_messages!(
(Call, Ack),
(CancelCall, Ack),
(Commit, Ack),
(RunGitHook, Ack),
(CopyProjectEntry, ProjectEntryResponse),
(CreateChannel, CreateChannelResponse),
(CreateProjectEntry, ProjectEntryResponse),
@@ -547,6 +549,7 @@ entity_messages!(
BufferSaved,
CloseBuffer,
Commit,
RunGitHook,
GetColorPresentation,
CopyProjectEntry,
CreateBufferForPeer,

View File

@@ -1327,7 +1327,7 @@ fn build_command(
let working_dir = RemotePathBuf::new(working_dir, ssh_path_style).to_string();
// shlex will wrap the command in single quotes (''), disabling ~ expansion,
// replace with with something that works
// replace with something that works
const TILDE_PREFIX: &'static str = "~/";
if working_dir.starts_with(TILDE_PREFIX) {
let working_dir = working_dir.trim_start_matches("~").trim_start_matches("/");

View File

@@ -251,11 +251,13 @@ impl WslRemoteConnection {
let mkdir = self.shell_kind.prepend_command_prefix("mkdir");
self.run_wsl_command(&mkdir, &["-p", &parent])
.await
.map_err(|e| anyhow!("Failed to create directory when uploading file: {}", e))?;
.context("Failed to create directory when uploading file")?;
}
let t0 = Instant::now();
let src_stat = fs::metadata(&src_path).await?;
let src_stat = fs::metadata(&src_path)
.await
.with_context(|| format!("source path does not exist: {}", src_path.display()))?;
let size = src_stat.len();
log::info!(
"uploading remote server to WSL {:?} ({}kb)",

View File

@@ -2,8 +2,6 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, fmt, path::Path, sync::LazyLock};
use crate::command::new_std_command;
/// Shell configuration to open the terminal with.
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)]
#[serde(rename_all = "snake_case")]
@@ -110,12 +108,16 @@ pub fn get_windows_system_shell() -> String {
use std::path::PathBuf;
fn find_pwsh_in_programfiles(find_alternate: bool, find_preview: bool) -> Option<PathBuf> {
#[cfg(target_pointer_width = "64")]
let env_var = if find_alternate {
if cfg!(target_pointer_width = "64") {
"ProgramFiles(x86)"
} else {
"ProgramW6432"
}
"ProgramFiles(x86)"
} else {
"ProgramFiles"
};
#[cfg(target_pointer_width = "32")]
let env_var = if find_alternate {
"ProgramW6432"
} else {
"ProgramFiles"
};
@@ -163,19 +165,23 @@ pub fn get_windows_system_shell() -> String {
} else {
"Microsoft.PowerShell_"
};
msix_app_dir.read_dir().ok()?.find_map(|entry| {
let entry = entry.ok()?;
if !matches!(entry.file_type(), Ok(ft) if ft.is_dir()) {
return None;
}
msix_app_dir
.read_dir()
.ok()?
.filter_map(|entry| {
let entry = entry.ok()?;
if !matches!(entry.file_type(), Ok(ft) if ft.is_dir()) {
return None;
}
if !entry.file_name().to_string_lossy().starts_with(prefix) {
return None;
}
if !entry.file_name().to_string_lossy().starts_with(prefix) {
return None;
}
let exe_path = entry.path().join("pwsh.exe");
exe_path.exists().then_some(exe_path)
})
let exe_path = entry.path().join("pwsh.exe");
exe_path.exists().then_some(exe_path)
})
.next()
}
fn find_pwsh_in_scoop() -> Option<PathBuf> {
@@ -184,37 +190,15 @@ pub fn get_windows_system_shell() -> String {
pwsh_exe.exists().then_some(pwsh_exe)
}
// check whether the found powershell is executable for us
static SYSTEM_SHELL: LazyLock<String> = LazyLock::new(|| {
let can_execute_pwsh = |p: &PathBuf| {
#[allow(clippy::disallowed_methods)]
let status = new_std_command(p).arg("-NoProfile").arg("-Help").status();
let success = status.as_ref().is_ok_and(|status| status.success());
if !success {
log::warn!(
"Powershell found at `{}` is not executable: {status:?}",
p.display()
);
}
success
};
let locations = [
|| find_pwsh_in_programfiles(false, false),
|| find_pwsh_in_programfiles(true, false),
|| find_pwsh_in_msix(false),
|| find_pwsh_in_programfiles(false, true),
|| find_pwsh_in_msix(true),
|| find_pwsh_in_programfiles(true, true),
|| find_pwsh_in_scoop(),
|| which::which_global("pwsh.exe").ok(),
|| which::which_global("powershell.exe").ok(),
];
locations
.into_iter()
.filter_map(|f| f())
.find(|p| can_execute_pwsh(&p))
.map(|p| p.to_string_lossy().trim().to_owned())
find_pwsh_in_programfiles(false, false)
.or_else(|| find_pwsh_in_programfiles(true, false))
.or_else(|| find_pwsh_in_msix(false))
.or_else(|| find_pwsh_in_programfiles(false, true))
.or_else(|| find_pwsh_in_msix(true))
.or_else(|| find_pwsh_in_programfiles(true, true))
.or_else(find_pwsh_in_scoop)
.map(|p| p.to_string_lossy().into_owned())
.inspect(|shell| log::info!("Found powershell in: {}", shell))
.unwrap_or_else(|| {
log::warn!("Powershell not found, falling back to `cmd`");

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
version = "0.214.0"
version = "0.214.2"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -1 +1 @@
dev
preview

View File

@@ -113,6 +113,8 @@ if [[ -n "${MACOS_CERTIFICATE:-}" && -n "${MACOS_CERTIFICATE_PASSWORD:-}" && -n
security create-keychain -p "$MACOS_CERTIFICATE_PASSWORD" zed.keychain || echo ""
security default-keychain -s zed.keychain
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" zed.keychain
# Calling set-keychain-settings without `-t` disables the auto-lock timeout
security set-keychain-settings zed.keychain
echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/zed-certificate.p12
security import /tmp/zed-certificate.p12 -k zed.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
rm /tmp/zed-certificate.p12

View File

@@ -204,7 +204,7 @@ function CollectFiles {
if($Architecture -eq "aarch64") {
New-Item -Type Directory -Path "$innoDir\arm64" -Force
Move-Item -Path ".\conpty\build\native\runtimes\arm64\OpenConsole.exe" -Destination "$innoDir\arm64\OpenConsole.exe" -Force
Move-Item -Path ".\conpty\runtimes\win10-arm64\native\conpty.dll" -Destination "$innoDir\conpty.dll" -Force
Move-Item -Path ".\conpty\runtimes\win-arm64\native\conpty.dll" -Destination "$innoDir\conpty.dll" -Force
}
else {
New-Item -Type Directory -Path "$innoDir\x64" -Force
@@ -212,7 +212,7 @@ function CollectFiles {
Move-Item -Path ".\AGS_SDK-6.3.0\ags_lib\lib\amd_ags_x64.dll" -Destination "$innoDir\amd_ags_x64.dll" -Force
Move-Item -Path ".\conpty\build\native\runtimes\x64\OpenConsole.exe" -Destination "$innoDir\x64\OpenConsole.exe" -Force
Move-Item -Path ".\conpty\build\native\runtimes\arm64\OpenConsole.exe" -Destination "$innoDir\arm64\OpenConsole.exe" -Force
Move-Item -Path ".\conpty\runtimes\win10-x64\native\conpty.dll" -Destination "$innoDir\conpty.dll" -Force
Move-Item -Path ".\conpty\runtimes\win-x64\native\conpty.dll" -Destination "$innoDir\conpty.dll" -Force
}
}