Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7a3a520c6b | ||
![]() |
b8339a9908 | ||
![]() |
eab4bd5720 | ||
![]() |
707bd7c156 | ||
![]() |
656f68dc69 | ||
![]() |
186334bb12 | ||
![]() |
b1324ebe1f | ||
![]() |
e9aec1d67a | ||
![]() |
32ed547c2c | ||
![]() |
39915f7c96 | ||
![]() |
2c2cea1c92 |
13 changed files with 369 additions and 198 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -8795,7 +8795,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.91.0"
|
||||
version = "0.91.4"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"ai",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(®ion.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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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::{
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -1 +1 @@
|
|||
dev
|
||||
stable
|
28
crates/zed/resources/zed.entitlements
Normal file
28
crates/zed/resources/zed.entitlements
Normal 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>
|
|
@ -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"))
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue