Compare commits

...
Sign in to create a new pull request.

11 commits

Author SHA1 Message Date
Max Brunsfeld
7a3a520c6b zed 0.91.4 2023-06-27 15:25:29 -07:00
Max Brunsfeld
b8339a9908 Fix bugs in handling combined injections exposed by HEEx (#2652)
Fixes
https://linear.app/zed-industries/issue/Z-2481/heex-this-snippet-triggers-a-hard-crash

Release Notes:

- Fixed a crash that would sometimes occur when editing a HEEx file
([#1703](https://github.com/zed-industries/community/issues/1703)).
2023-06-27 15:22:06 -07:00
Joseph T. Lyons
eab4bd5720 v0.91.x stable 2023-06-21 13:58:53 -04:00
Antonio Scandurra
707bd7c156 zed 0.91.3 2023-06-16 17:27:42 +02:00
Antonio Scandurra
656f68dc69 Activate screen-sharing when leader activates a panel (#2614)
Fixes
https://linear.app/zed-industries/issue/Z-1875/screen-sharing-tab-is-not-activated-when-leader-is-on-a-panel

Release Notes:

- Fixed a bug that caused followers to not see the leader's screen when
they activated a panel.
2023-06-16 17:26:33 +02:00
Max Brunsfeld
186334bb12 zed 0.91.2 2023-06-15 15:34:43 -07:00
Max Brunsfeld
b1324ebe1f Fix failure to upload panics when multiple panics happen at the same time (#2616)
When multiple panics occur at the same time (usually because one thread
panics, and another thread joins it), multiple panic JSON objects can
get written to the same panic file. The resulting file won't be valid
JSON.

This PR addresses that problem via two changes:
* Format panic files as single-line JSON objects
* When a panic file  isn't valid JSON, try taking the first line

In the future, we could try combining all of the backtraces, but for
now, I just want to avoid a problem of not reporting a panic at all.

Release Notes:

- Fixed a problem with Zed's internal crash reporting.
2023-06-15 15:34:09 -07:00
Max Brunsfeld
e9aec1d67a Don't rely on debug symbols for panic reporting (#2615)
This fixes a regression introduced in
https://github.com/zed-industries/zed/pull/2560, where panic reports did
not include backtraces. The problem was that in that PR, I assumed we
could retrieve file paths for symbols in our backtraces. But actually,
that functionality only works when the app is built locally, and a
`.dSYM` file can be magically found by the OS. We don't ship those dSYM
files with Zed, so panic symbols do not have file paths available.

Panic backtraces will still be more useful and less noisy than before
though: we will strip out frames for which we don't have symbol names,
and remove leading panic-handling stack frames from the backtraces.

Release Notes:

- N/A
2023-06-15 15:34:01 -07:00
Mikayla Maki
32ed547c2c
zed 0.91.1 2023-06-14 18:10:16 -07:00
Mikayla Maki
39915f7c96
Add entitlements file to bundle step (#2611)
This completes the bundle changes that will be needed to access voice,
as well as adds permissions for accessing other MacOS services, the
camera, and the necessary permissions for plugins. This was developed by
combining the entitlements of iTerm and VSCode, cross-referenced with
the entitlements of Firefox. 

Release Notes:

- Fixed a bug in enabling authorization for macOS services (preview
only)
2023-06-14 18:09:15 -07:00
Joseph Lyons
2c2cea1c92 v0.91.x preview 2023-06-14 13:27:18 -04:00
13 changed files with 369 additions and 198 deletions

2
Cargo.lock generated
View file

@ -8795,7 +8795,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.91.0"
version = "0.91.4"
dependencies = [
"activity_indicator",
"ai",

View file

@ -39,7 +39,12 @@ use std::{
},
};
use unindent::Unindent as _;
use workspace::{item::ItemHandle as _, shared_screen::SharedScreen, SplitDirection, Workspace};
use workspace::{
dock::{test::TestPanel, DockPosition},
item::{test::TestItem, ItemHandle as _},
shared_screen::SharedScreen,
SplitDirection, Workspace,
};
#[ctor::ctor]
fn init_logger() {
@ -6847,12 +6852,43 @@ async fn test_basic_following(
)
});
// Client B activates an external window again, and the previously-opened screen-sharing item
// gets activated.
active_call_b
.update(cx_b, |call, cx| call.set_location(None, cx))
.await
.unwrap();
// Client B activates a panel, and the previously-opened screen-sharing item gets activated.
let panel = cx_b.add_view(workspace_b.window_id(), |_| {
TestPanel::new(DockPosition::Left)
});
workspace_b.update(cx_b, |workspace, cx| {
workspace.add_panel(panel, cx);
workspace.toggle_panel_focus::<TestPanel>(cx);
});
deterministic.run_until_parked();
assert_eq!(
workspace_a.read_with(cx_a, |workspace, cx| workspace
.active_item(cx)
.unwrap()
.id()),
shared_screen.id()
);
// Toggling the focus back to the pane causes client A to return to the multibuffer.
workspace_b.update(cx_b, |workspace, cx| {
workspace.toggle_panel_focus::<TestPanel>(cx);
});
deterministic.run_until_parked();
workspace_a.read_with(cx_a, |workspace, cx| {
assert_eq!(
workspace.active_item(cx).unwrap().id(),
multibuffer_editor_a.id()
)
});
// Client B activates an item that doesn't implement following,
// so the previously-opened screen-sharing item gets activated.
let unfollowable_item = cx_b.add_view(workspace_b.window_id(), |_| TestItem::new());
workspace_b.update(cx_b, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
})
});
deterministic.run_until_parked();
assert_eq!(
workspace_a.read_with(cx_a, |workspace, cx| workspace

View file

@ -11,7 +11,7 @@ use std::{
cell::RefCell,
cmp::{self, Ordering, Reverse},
collections::BinaryHeap,
iter,
fmt, iter,
ops::{Deref, DerefMut, Range},
sync::Arc,
};
@ -428,6 +428,8 @@ impl SyntaxSnapshot {
invalidated_ranges: Vec<Range<usize>>,
registry: Option<&Arc<LanguageRegistry>>,
) {
log::trace!("reparse. invalidated ranges:{:?}", invalidated_ranges);
let max_depth = self.layers.summary().max_depth;
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
cursor.next(&text);
@ -489,6 +491,15 @@ impl SyntaxSnapshot {
let Some(layer) = cursor.item() else { break };
if changed_regions.intersects(&layer, text) {
if let SyntaxLayerContent::Parsed { language, .. } = &layer.content {
log::trace!(
"discard layer. language:{}, range:{:?}. changed_regions:{:?}",
language.name(),
LogAnchorRange(&layer.range, text),
LogChangedRegions(&changed_regions, text),
);
}
changed_regions.insert(
ChangedRegion {
depth: layer.depth + 1,
@ -541,26 +552,24 @@ impl SyntaxSnapshot {
.to_ts_point();
}
if included_ranges.is_empty() {
included_ranges.push(tree_sitter::Range {
start_byte: 0,
end_byte: 0,
start_point: Default::default(),
end_point: Default::default(),
});
}
if let Some(SyntaxLayerContent::Parsed { tree: old_tree, .. }) =
old_layer.map(|layer| &layer.content)
if let Some((SyntaxLayerContent::Parsed { tree: old_tree, .. }, layer_start)) =
old_layer.map(|layer| (&layer.content, layer.range.start))
{
log::trace!(
"existing layer. language:{}, start:{:?}, ranges:{:?}",
language.name(),
LogPoint(layer_start.to_point(&text)),
LogIncludedRanges(&old_tree.included_ranges())
);
if let ParseMode::Combined {
mut parent_layer_changed_ranges,
..
} = step.mode
{
for range in &mut parent_layer_changed_ranges {
range.start -= step_start_byte;
range.end -= step_start_byte;
range.start = range.start.saturating_sub(step_start_byte);
range.end = range.end.saturating_sub(step_start_byte);
}
included_ranges = splice_included_ranges(
@ -570,6 +579,22 @@ impl SyntaxSnapshot {
);
}
if included_ranges.is_empty() {
included_ranges.push(tree_sitter::Range {
start_byte: 0,
end_byte: 0,
start_point: Default::default(),
end_point: Default::default(),
});
}
log::trace!(
"update layer. language:{}, start:{:?}, ranges:{:?}",
language.name(),
LogAnchorRange(&step.range, text),
LogIncludedRanges(&included_ranges),
);
tree = parse_text(
grammar,
text.as_rope(),
@ -586,6 +611,22 @@ impl SyntaxSnapshot {
}),
);
} else {
if included_ranges.is_empty() {
included_ranges.push(tree_sitter::Range {
start_byte: 0,
end_byte: 0,
start_point: Default::default(),
end_point: Default::default(),
});
}
log::trace!(
"create layer. language:{}, range:{:?}, included_ranges:{:?}",
language.name(),
LogAnchorRange(&step.range, text),
LogIncludedRanges(&included_ranges),
);
tree = parse_text(
grammar,
text.as_rope(),
@ -613,6 +654,7 @@ impl SyntaxSnapshot {
get_injections(
config,
text,
step.range.clone(),
tree.root_node_with_offset(
step_start_byte,
step_start_point.to_ts_point(),
@ -1117,6 +1159,7 @@ fn parse_text(
fn get_injections(
config: &InjectionConfig,
text: &BufferSnapshot,
outer_range: Range<Anchor>,
node: Node,
language_registry: &Arc<LanguageRegistry>,
depth: usize,
@ -1153,16 +1196,17 @@ fn get_injections(
continue;
}
// Avoid duplicate matches if two changed ranges intersect the same injection.
let content_range =
content_ranges.first().unwrap().start_byte..content_ranges.last().unwrap().end_byte;
if let Some((last_pattern_ix, last_range)) = &prev_match {
if mat.pattern_index == *last_pattern_ix && content_range == *last_range {
// Avoid duplicate matches if two changed ranges intersect the same injection.
if let Some((prev_pattern_ix, prev_range)) = &prev_match {
if mat.pattern_index == *prev_pattern_ix && content_range == *prev_range {
continue;
}
}
prev_match = Some((mat.pattern_index, content_range.clone()));
prev_match = Some((mat.pattern_index, content_range.clone()));
let combined = config.patterns[mat.pattern_index].combined;
let mut language_name = None;
@ -1218,11 +1262,10 @@ fn get_injections(
for (language, mut included_ranges) in combined_injection_ranges.drain() {
included_ranges.sort_unstable();
let range = text.anchor_before(node.start_byte())..text.anchor_after(node.end_byte());
queue.push(ParseStep {
depth,
language: ParseStepLanguage::Loaded { language },
range,
range: outer_range.clone(),
included_ranges,
mode: ParseMode::Combined {
parent_layer_range: node.start_byte()..node.end_byte(),
@ -1234,72 +1277,77 @@ fn get_injections(
pub(crate) fn splice_included_ranges(
mut ranges: Vec<tree_sitter::Range>,
changed_ranges: &[Range<usize>],
removed_ranges: &[Range<usize>],
new_ranges: &[tree_sitter::Range],
) -> Vec<tree_sitter::Range> {
let mut changed_ranges = changed_ranges.into_iter().peekable();
let mut new_ranges = new_ranges.into_iter().peekable();
let mut removed_ranges = removed_ranges.iter().cloned().peekable();
let mut new_ranges = new_ranges.into_iter().cloned().peekable();
let mut ranges_ix = 0;
loop {
let new_range = new_ranges.peek();
let mut changed_range = changed_ranges.peek();
let next_new_range = new_ranges.peek();
let next_removed_range = removed_ranges.peek();
// Remove ranges that have changed before inserting any new ranges
// into those ranges.
if let Some((changed, new)) = changed_range.zip(new_range) {
if new.end_byte < changed.start {
changed_range = None;
let (remove, insert) = match (next_removed_range, next_new_range) {
(None, None) => break,
(Some(_), None) => (removed_ranges.next().unwrap(), None),
(Some(next_removed_range), Some(next_new_range)) => {
if next_removed_range.end < next_new_range.start_byte {
(removed_ranges.next().unwrap(), None)
} else {
let mut start = next_new_range.start_byte;
let mut end = next_new_range.end_byte;
while let Some(next_removed_range) = removed_ranges.peek() {
if next_removed_range.start > next_new_range.end_byte {
break;
}
let next_removed_range = removed_ranges.next().unwrap();
start = cmp::min(start, next_removed_range.start);
end = cmp::max(end, next_removed_range.end);
}
(start..end, Some(new_ranges.next().unwrap()))
}
}
(None, Some(next_new_range)) => (
next_new_range.start_byte..next_new_range.end_byte,
Some(new_ranges.next().unwrap()),
),
};
let mut start_ix = ranges_ix
+ match ranges[ranges_ix..].binary_search_by_key(&remove.start, |r| r.end_byte) {
Ok(ix) => ix,
Err(ix) => ix,
};
let mut end_ix = ranges_ix
+ match ranges[ranges_ix..].binary_search_by_key(&remove.end, |r| r.start_byte) {
Ok(ix) => ix + 1,
Err(ix) => ix,
};
// If there are empty ranges, then there may be multiple ranges with the same
// start or end. Expand the splice to include any adjacent ranges that touch
// the changed range.
while start_ix > 0 {
if ranges[start_ix - 1].end_byte == remove.start {
start_ix -= 1;
} else {
break;
}
}
while let Some(range) = ranges.get(end_ix) {
if range.start_byte == remove.end {
end_ix += 1;
} else {
break;
}
}
if let Some(changed) = changed_range {
let mut start_ix = ranges_ix
+ match ranges[ranges_ix..].binary_search_by_key(&changed.start, |r| r.end_byte) {
Ok(ix) | Err(ix) => ix,
};
let mut end_ix = ranges_ix
+ match ranges[ranges_ix..].binary_search_by_key(&changed.end, |r| r.start_byte) {
Ok(ix) => ix + 1,
Err(ix) => ix,
};
// If there are empty ranges, then there may be multiple ranges with the same
// start or end. Expand the splice to include any adjacent ranges that touch
// the changed range.
while start_ix > 0 {
if ranges[start_ix - 1].end_byte == changed.start {
start_ix -= 1;
} else {
break;
}
}
while let Some(range) = ranges.get(end_ix) {
if range.start_byte == changed.end {
end_ix += 1;
} else {
break;
}
}
if end_ix > start_ix {
ranges.splice(start_ix..end_ix, []);
}
changed_ranges.next();
ranges_ix = start_ix;
} else if let Some(new_range) = new_range {
let ix = ranges_ix
+ match ranges[ranges_ix..]
.binary_search_by_key(&new_range.start_byte, |r| r.start_byte)
{
Ok(ix) | Err(ix) => ix,
};
ranges.insert(ix, **new_range);
new_ranges.next();
ranges_ix = ix + 1;
} else {
break;
}
ranges.splice(start_ix..end_ix, insert);
ranges_ix = start_ix;
}
ranges
}
@ -1628,3 +1676,46 @@ impl ToTreeSitterPoint for Point {
Point::new(point.row as u32, point.column as u32)
}
}
struct LogIncludedRanges<'a>(&'a [tree_sitter::Range]);
struct LogPoint(Point);
struct LogAnchorRange<'a>(&'a Range<Anchor>, &'a text::BufferSnapshot);
struct LogChangedRegions<'a>(&'a ChangeRegionSet, &'a text::BufferSnapshot);
impl<'a> fmt::Debug for LogIncludedRanges<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(self.0.iter().map(|range| {
let start = range.start_point;
let end = range.end_point;
(start.row, start.column)..(end.row, end.column)
}))
.finish()
}
}
impl<'a> fmt::Debug for LogAnchorRange<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let range = self.0.to_point(self.1);
(LogPoint(range.start)..LogPoint(range.end)).fmt(f)
}
}
impl<'a> fmt::Debug for LogChangedRegions<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(
self.0
.0
.iter()
.map(|region| LogAnchorRange(&region.range, self.1)),
)
.finish()
}
}
impl fmt::Debug for LogPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(self.0.row, self.0.column).fmt(f)
}
}

View file

@ -48,6 +48,13 @@ fn test_splice_included_ranges() {
let new_ranges = splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]);
assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]);
// does not create overlapping ranges
let new_ranges = splice_included_ranges(ranges.clone(), &[0..18], &[ts_range(20..32)]);
assert_eq!(
new_ranges,
&[ts_range(20..32), ts_range(50..60), ts_range(80..90)]
);
fn ts_range(range: Range<usize>) -> tree_sitter::Range {
tree_sitter::Range {
start_byte: range.start,
@ -624,6 +631,26 @@ fn test_combined_injections_splitting_some_injections() {
);
}
#[gpui::test]
fn test_combined_injections_editing_after_last_injection() {
test_edit_sequence(
"ERB",
&[
r#"
<% foo %>
<div></div>
<% bar %>
"#,
r#"
<% foo %>
<div></div>
<% bar %>«
more text»
"#,
],
);
}
#[gpui::test]
fn test_combined_injections_inside_injections() {
let (_buffer, _syntax_map) = test_edit_sequence(
@ -974,13 +1001,16 @@ fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap
mutated_syntax_map.reparse(language.clone(), &buffer);
for (i, marked_string) in steps.into_iter().enumerate() {
buffer.edit_via_marked_text(&marked_string.unindent());
let marked_string = marked_string.unindent();
log::info!("incremental parse {i}: {marked_string:?}");
buffer.edit_via_marked_text(&marked_string);
// Reparse the syntax map
mutated_syntax_map.interpolate(&buffer);
mutated_syntax_map.reparse(language.clone(), &buffer);
// Create a second syntax map from scratch
log::info!("fresh parse {i}: {marked_string:?}");
let mut reference_syntax_map = SyntaxMap::new();
reference_syntax_map.set_language_registry(registry.clone());
reference_syntax_map.reparse(language.clone(), &buffer);
@ -1133,6 +1163,7 @@ fn range_for_text(buffer: &Buffer, text: &str) -> Range<usize> {
start..start + text.len()
}
#[track_caller]
fn assert_layers_for_range(
syntax_map: &SyntaxMap,
buffer: &BufferSnapshot,

View file

@ -598,8 +598,8 @@ impl StatusItemView for PanelButtons {
}
}
#[cfg(test)]
pub(crate) mod test {
#[cfg(any(test, feature = "test-support"))]
pub mod test {
use super::*;
use gpui::{ViewContext, WindowContext};

View file

@ -710,8 +710,8 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
}
}
#[cfg(test)]
pub(crate) mod test {
#[cfg(any(test, feature = "test-support"))]
pub mod test {
use super::{Item, ItemEvent};
use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
use gpui::{

View file

@ -919,6 +919,7 @@ impl Workspace {
this.zoomed = None;
this.zoomed_position = None;
}
this.update_active_view_for_followers(cx);
cx.notify();
}
}
@ -1946,18 +1947,7 @@ impl Workspace {
self.zoomed = None;
}
self.zoomed_position = None;
self.update_followers(
proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
id: self.active_item(cx).and_then(|item| {
item.to_followable_item_handle(cx)?
.remote_id(&self.app_state.client, cx)
.map(|id| id.to_proto())
}),
leader_id: self.leader_for_pane(&pane),
}),
cx,
);
self.update_active_view_for_followers(cx);
cx.notify();
}
@ -2646,6 +2636,30 @@ impl Workspace {
Ok(())
}
fn update_active_view_for_followers(&self, cx: &AppContext) {
if self.active_pane.read(cx).has_focus() {
self.update_followers(
proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
id: self.active_item(cx).and_then(|item| {
item.to_followable_item_handle(cx)?
.remote_id(&self.app_state.client, cx)
.map(|id| id.to_proto())
}),
leader_id: self.leader_for_pane(&self.active_pane),
}),
cx,
);
} else {
self.update_followers(
proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
id: None,
leader_id: None,
}),
cx,
);
}
}
fn update_followers(
&self,
update: proto::update_followers::Variant,
@ -2693,12 +2707,10 @@ impl Workspace {
.and_then(|id| state.items_by_leader_view_id.get(&id))
{
items_to_activate.push((pane.clone(), item.boxed_clone()));
} else {
if let Some(shared_screen) =
self.shared_screen_for_peer(leader_id, pane, cx)
{
items_to_activate.push((pane.clone(), Box::new(shared_screen)));
}
} else if let Some(shared_screen) =
self.shared_screen_for_peer(leader_id, pane, cx)
{
items_to_activate.push((pane.clone(), Box::new(shared_screen)));
}
}
}

View file

@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.91.0"
version = "0.91.4"
publish = false
[lib]

View file

@ -1 +1 @@
dev
stable

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.personal-information.addressbook</key>
<true/>
<key>com.apple.security.personal-information.calendars</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
<key>com.apple.security.personal-information.photos-library</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View file

@ -7,7 +7,5 @@
((directive (expression_value) @content)
(#set! language "elixir"))
; expressions live within HTML tags, and do not need to be combined
; <link href={ Routes.static_path(..) } />
((expression (expression_value) @content)
(#set! language "elixir"))

View file

@ -31,7 +31,6 @@ use std::{
ffi::OsStr,
fs::OpenOptions,
io::Write as _,
ops::Not,
os::unix::prelude::OsStrExt,
panic,
path::{Path, PathBuf},
@ -373,7 +372,6 @@ struct Panic {
os_version: Option<String>,
architecture: String,
panicked_on: u128,
identifying_backtrace: Option<Vec<String>>,
}
#[derive(Serialize)]
@ -401,61 +399,18 @@ fn init_panic_hook(app: &App) {
.unwrap_or_else(|| "Box<Any>".to_string());
let backtrace = Backtrace::new();
let backtrace = backtrace
let mut backtrace = backtrace
.frames()
.iter()
.filter_map(|frame| {
let symbol = frame.symbols().first()?;
let path = symbol.filename()?;
Some((path, symbol.lineno(), format!("{:#}", symbol.name()?)))
})
.filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?)))
.collect::<Vec<_>>();
let this_file_path = Path::new(file!());
// Find the first frame in the backtrace for this panic hook itself. Exclude
// that frame and all frames before it.
let mut start_frame_ix = 0;
let mut codebase_root_path = None;
for (ix, (path, _, _)) in backtrace.iter().enumerate() {
if path.ends_with(this_file_path) {
start_frame_ix = ix + 1;
codebase_root_path = path.ancestors().nth(this_file_path.components().count());
break;
}
}
// Exclude any subsequent frames inside of rust's panic handling system.
while let Some((path, _, _)) = backtrace.get(start_frame_ix) {
if path.starts_with("/rustc") {
start_frame_ix += 1;
} else {
break;
}
}
// Build two backtraces:
// * one for display, which includes symbol names for all frames, and files
// and line numbers for symbols in this codebase
// * one for identification and de-duplication, which only includes symbol
// names for symbols in this codebase.
let mut display_backtrace = Vec::new();
let mut identifying_backtrace = Vec::new();
for (path, line, symbol) in &backtrace[start_frame_ix..] {
display_backtrace.push(symbol.clone());
if let Some(codebase_root_path) = &codebase_root_path {
if let Ok(suffix) = path.strip_prefix(&codebase_root_path) {
identifying_backtrace.push(symbol.clone());
let display_path = suffix.to_string_lossy();
if let Some(line) = line {
display_backtrace.push(format!(" {display_path}:{line}"));
} else {
display_backtrace.push(format!(" {display_path}"));
}
}
}
// Strip out leading stack frames for rust panic-handling.
if let Some(ix) = backtrace
.iter()
.position(|name| name == "rust_begin_unwind")
{
backtrace.drain(0..=ix);
}
let panic_data = Panic {
@ -477,29 +432,27 @@ fn init_panic_hook(app: &App) {
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis(),
backtrace: display_backtrace,
identifying_backtrace: identifying_backtrace
.is_empty()
.not()
.then_some(identifying_backtrace),
backtrace,
};
if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
if is_pty {
if is_pty {
if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
eprintln!("{}", panic_data_json);
return;
}
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
let panic_file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(&panic_file_path)
.log_err();
if let Some(mut panic_file) = panic_file {
write!(&mut panic_file, "{}", panic_data_json).log_err();
panic_file.flush().log_err();
} else {
if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
let panic_file = std::fs::OpenOptions::new()
.append(true)
.create(true)
.open(&panic_file_path)
.log_err();
if let Some(mut panic_file) = panic_file {
writeln!(&mut panic_file, "{}", panic_data_json).log_err();
panic_file.flush().log_err();
}
}
}
}));
@ -531,23 +484,45 @@ fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
}
if telemetry_settings.diagnostics {
let panic_data_text = smol::fs::read_to_string(&child_path)
let panic_file_content = smol::fs::read_to_string(&child_path)
.await
.context("error reading panic file")?;
let body = serde_json::to_string(&PanicRequest {
panic: serde_json::from_str(&panic_data_text)?,
token: ZED_SECRET_CLIENT_TOKEN.into(),
})
.unwrap();
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
});
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 let Some(panic) = panic {
let body = serde_json::to_string(&PanicRequest {
panic,
token: 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()
);
}
}
}

View file

@ -81,12 +81,12 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR
security import /tmp/zed-certificate.p12 -k zed.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
rm /tmp/zed-certificate.p12
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CERTIFICATE_PASSWORD" zed.keychain
/usr/bin/codesign --force --deep --timestamp --options runtime --sign "Zed Industries, Inc." "${app_path}" -v
/usr/bin/codesign --force --deep --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}" -v
security default-keychain -s login.keychain
else
echo "One or more of the following variables are missing: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PASSWORD, APPLE_NOTARIZATION_USERNAME, APPLE_NOTARIZATION_PASSWORD"
echo "Performing an ad-hoc signature, but this bundle should not be distributed"
codesign --force --deep --sign - "${app_path}" -v
codesign --force --deep --entitlements crates/zed/resources/zed.entitlements --sign - "${app_path}" -v
fi
if [ "$target_dir" = "debug" ]; then