Merge branch 'main' into fix-popin-for-project-panel
This commit is contained in:
commit
20b58fea35
72 changed files with 435 additions and 37480 deletions
|
@ -920,6 +920,39 @@ impl AssistantPanel {
|
|||
self.editors.get(self.active_editor_index?)
|
||||
}
|
||||
|
||||
fn render_api_key_editor(
|
||||
&self,
|
||||
editor: &View<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: if editor.read(cx).read_only(cx) {
|
||||
cx.theme().colors().text_disabled
|
||||
} else {
|
||||
cx.theme().colors().text
|
||||
},
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features,
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: FontWeight::NORMAL,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.3).into(),
|
||||
background_color: None,
|
||||
underline: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
};
|
||||
EditorElement::new(
|
||||
&editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().colors().editor_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn render_hamburger_button(cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
IconButton::new("hamburger_button", IconName::Menu)
|
||||
.on_click(cx.listener(|this, _event, cx| {
|
||||
|
@ -1091,28 +1124,42 @@ fn build_api_key_editor(cx: &mut ViewContext<AssistantPanel>) -> View<Editor> {
|
|||
impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
if let Some(api_key_editor) = self.api_key_editor.clone() {
|
||||
const INSTRUCTIONS: [&'static str; 5] = [
|
||||
"To use the assistant panel or inline assistant, you need to add your OpenAI API key.",
|
||||
" - You can create an API key at: platform.openai.com/api-keys",
|
||||
" - Having a subscription for another service like GitHub Copilot won't work.",
|
||||
" ",
|
||||
"Paste your OpenAI API key and press Enter to use the assistant:"
|
||||
];
|
||||
|
||||
v_flex()
|
||||
.p_4()
|
||||
.size_full()
|
||||
.on_action(cx.listener(AssistantPanel::save_credentials))
|
||||
.track_focus(&self.focus_handle)
|
||||
.child(Label::new(
|
||||
"To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
|
||||
))
|
||||
.child(Label::new(
|
||||
" - Having a subscription for another service like GitHub Copilot won't work."
|
||||
))
|
||||
.child(Label::new(
|
||||
" - You can create a api key at: platform.openai.com/api-keys"
|
||||
))
|
||||
.child(Label::new(
|
||||
" "
|
||||
))
|
||||
.child(Label::new(
|
||||
"Paste your OpenAI API key and press Enter to use the assistant"
|
||||
))
|
||||
.child(api_key_editor)
|
||||
.child(Label::new(
|
||||
"Click on the Z button in the status bar to close this panel."
|
||||
))
|
||||
.children(
|
||||
INSTRUCTIONS.map(|instruction| Label::new(instruction).size(LabelSize::Small)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.my_2()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.rounded_md()
|
||||
.child(self.render_api_key_editor(&api_key_editor, cx)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Label::new("Click on").size(LabelSize::Small))
|
||||
.child(Icon::new(IconName::Ai).size(IconSize::XSmall))
|
||||
.child(
|
||||
Label::new("in the status bar to close this panel.")
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
let header = TabBar::new("assistant_header")
|
||||
.start_child(
|
||||
|
|
|
@ -52,6 +52,7 @@ pub use user::*;
|
|||
lazy_static! {
|
||||
pub static ref ZED_SERVER_URL: String =
|
||||
std::env::var("ZED_SERVER_URL").unwrap_or_else(|_| "https://zed.dev".to_string());
|
||||
pub static ref ZED_RPC_URL: Option<String> = std::env::var("ZED_RPC_URL").ok();
|
||||
pub static ref IMPERSONATE_LOGIN: Option<String> = std::env::var("ZED_IMPERSONATE")
|
||||
.ok()
|
||||
.and_then(|s| if s.is_empty() { None } else { Some(s) });
|
||||
|
@ -933,6 +934,10 @@ impl Client {
|
|||
http: Arc<dyn HttpClient>,
|
||||
release_channel: Option<ReleaseChannel>,
|
||||
) -> Result<Url> {
|
||||
if let Some(url) = &*ZED_RPC_URL {
|
||||
return Url::parse(url).context("invalid rpc url");
|
||||
}
|
||||
|
||||
let mut url = format!("{}/rpc", *ZED_SERVER_URL);
|
||||
if let Some(preview_param) =
|
||||
release_channel.and_then(|channel| channel.release_query_param())
|
||||
|
@ -941,14 +946,6 @@ impl Client {
|
|||
url += preview_param;
|
||||
}
|
||||
let response = http.get(&url, Default::default(), false).await?;
|
||||
|
||||
// Normally, ZED_SERVER_URL is set to the URL of zed.dev website.
|
||||
// The website's /rpc endpoint redirects to a collab server's /rpc endpoint,
|
||||
// which requires authorization via an HTTP header.
|
||||
//
|
||||
// For testing purposes, ZED_SERVER_URL can also set to the direct URL of
|
||||
// of a collab server. In that case, a request to the /rpc endpoint will
|
||||
// return an 'unauthorized' response.
|
||||
let collab_url = if response.status().is_redirection() {
|
||||
response
|
||||
.headers()
|
||||
|
@ -957,8 +954,6 @@ impl Client {
|
|||
.to_str()
|
||||
.map_err(EstablishConnectionError::other)?
|
||||
.to_string()
|
||||
} else if response.status() == StatusCode::UNAUTHORIZED {
|
||||
url
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"unexpected /rpc response status {}",
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
["nathansobo", "as-cii", "maxbrunsfeld", "iamnbutler"]
|
||||
[
|
||||
"nathansobo",
|
||||
"as-cii",
|
||||
"maxbrunsfeld",
|
||||
"iamnbutler",
|
||||
"mikayla-maki",
|
||||
"JosephTLyons"
|
||||
]
|
||||
|
|
|
@ -17,9 +17,8 @@ use gpui::{
|
|||
actions, canvas, div, fill, list, overlay, point, prelude::*, px, AnyElement, AppContext,
|
||||
AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle,
|
||||
FocusableView, FontStyle, FontWeight, InteractiveElement, IntoElement, ListOffset, ListState,
|
||||
Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce,
|
||||
SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext,
|
||||
WeakView, WhiteSpace,
|
||||
Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, SharedString, Styled,
|
||||
Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, WhiteSpace,
|
||||
};
|
||||
use menu::{Cancel, Confirm, SelectNext, SelectPrev};
|
||||
use project::{Fs, Project};
|
||||
|
@ -2296,7 +2295,7 @@ impl CollabPanel {
|
|||
h_flex()
|
||||
.id(channel_id as usize)
|
||||
.child(Label::new(channel.name.clone()))
|
||||
.children(face_pile.map(|face_pile| face_pile.render(cx))),
|
||||
.children(face_pile.map(|face_pile| face_pile.render().p_1())),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
|
|
@ -57,6 +57,7 @@ impl Render for CollabTitlebarItem {
|
|||
let current_user = self.user_store.read(cx).current_user();
|
||||
let client = self.client.clone();
|
||||
let project_id = self.project.read(cx).remote_id();
|
||||
let workspace = self.workspace.upgrade();
|
||||
|
||||
h_flex()
|
||||
.id("titlebar")
|
||||
|
@ -100,6 +101,7 @@ impl Render for CollabTitlebarItem {
|
|||
true,
|
||||
room.is_speaking(),
|
||||
room.is_muted(),
|
||||
None,
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
|
@ -113,6 +115,12 @@ impl Render for CollabTitlebarItem {
|
|||
}))
|
||||
.children(
|
||||
remote_participants.iter().filter_map(|collaborator| {
|
||||
let player_color = player_colors
|
||||
.color_for_participant(collaborator.participant_index.0);
|
||||
let is_following = workspace
|
||||
.as_ref()?
|
||||
.read(cx)
|
||||
.is_being_followed(collaborator.peer_id);
|
||||
let is_present = project_id.map_or(false, |project_id| {
|
||||
collaborator.location
|
||||
== ParticipantLocation::SharedProject { project_id }
|
||||
|
@ -124,6 +132,7 @@ impl Render for CollabTitlebarItem {
|
|||
is_present,
|
||||
collaborator.speaking,
|
||||
collaborator.muted,
|
||||
is_following.then_some(player_color.selection),
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
|
@ -134,13 +143,7 @@ impl Render for CollabTitlebarItem {
|
|||
v_flex()
|
||||
.id(("collaborator", collaborator.user.id))
|
||||
.child(face_pile)
|
||||
.child(render_color_ribbon(
|
||||
player_colors
|
||||
.color_for_participant(
|
||||
collaborator.participant_index.0,
|
||||
)
|
||||
.cursor,
|
||||
))
|
||||
.child(render_color_ribbon(player_color.cursor))
|
||||
.cursor_pointer()
|
||||
.on_click({
|
||||
let peer_id = collaborator.peer_id;
|
||||
|
@ -468,11 +471,12 @@ impl CollabTitlebarItem {
|
|||
is_present: bool,
|
||||
is_speaking: bool,
|
||||
is_muted: bool,
|
||||
leader_selection_color: Option<Hsla>,
|
||||
room: &Room,
|
||||
project_id: Option<u64>,
|
||||
current_user: &Arc<User>,
|
||||
cx: &ViewContext<Self>,
|
||||
) -> Option<FacePile> {
|
||||
) -> Option<Div> {
|
||||
if room.role_for_user(user.id) == Some(proto::ChannelRole::Guest) {
|
||||
return None;
|
||||
}
|
||||
|
@ -481,56 +485,72 @@ impl CollabTitlebarItem {
|
|||
let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
|
||||
let extra_count = followers.len().saturating_sub(FACEPILE_LIMIT);
|
||||
|
||||
let pile = FacePile::default()
|
||||
.child(
|
||||
Avatar::new(user.avatar_uri.clone())
|
||||
.grayscale(!is_present)
|
||||
.border_color(if is_speaking {
|
||||
cx.theme().status().info
|
||||
} else {
|
||||
// We draw the border in a transparent color rather to avoid
|
||||
// the layout shift that would come with adding/removing the border.
|
||||
gpui::transparent_black()
|
||||
})
|
||||
.when(is_muted, |avatar| {
|
||||
avatar.indicator(
|
||||
AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted).tooltip({
|
||||
let github_login = user.github_login.clone();
|
||||
move |cx| Tooltip::text(format!("{} is muted", github_login), cx)
|
||||
}),
|
||||
Some(
|
||||
div()
|
||||
.m_0p5()
|
||||
.p_0p5()
|
||||
// When the collaborator is not followed, still draw this wrapper div, but leave
|
||||
// it transparent, so that it does not shift the layout when following.
|
||||
.when_some(leader_selection_color, |div, color| {
|
||||
div.rounded_md().bg(color)
|
||||
})
|
||||
.child(
|
||||
FacePile::default()
|
||||
.child(
|
||||
Avatar::new(user.avatar_uri.clone())
|
||||
.grayscale(!is_present)
|
||||
.border_color(if is_speaking {
|
||||
cx.theme().status().info
|
||||
} else {
|
||||
// We draw the border in a transparent color rather to avoid
|
||||
// the layout shift that would come with adding/removing the border.
|
||||
gpui::transparent_black()
|
||||
})
|
||||
.when(is_muted, |avatar| {
|
||||
avatar.indicator(
|
||||
AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted)
|
||||
.tooltip({
|
||||
let github_login = user.github_login.clone();
|
||||
move |cx| {
|
||||
Tooltip::text(
|
||||
format!("{} is muted", github_login),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.children(
|
||||
followers
|
||||
.iter()
|
||||
.take(FACEPILE_LIMIT)
|
||||
.filter_map(|follower_peer_id| {
|
||||
let follower = room
|
||||
.remote_participants()
|
||||
.values()
|
||||
.find_map(|p| (p.peer_id == *follower_peer_id).then_some(&p.user))
|
||||
.or_else(|| {
|
||||
(self.client.peer_id() == Some(*follower_peer_id))
|
||||
.then_some(current_user)
|
||||
})?
|
||||
.clone();
|
||||
.children(followers.iter().take(FACEPILE_LIMIT).filter_map(
|
||||
|follower_peer_id| {
|
||||
let follower = room
|
||||
.remote_participants()
|
||||
.values()
|
||||
.find_map(|p| {
|
||||
(p.peer_id == *follower_peer_id).then_some(&p.user)
|
||||
})
|
||||
.or_else(|| {
|
||||
(self.client.peer_id() == Some(*follower_peer_id))
|
||||
.then_some(current_user)
|
||||
})?
|
||||
.clone();
|
||||
|
||||
Some(Avatar::new(follower.avatar_uri.clone()))
|
||||
}),
|
||||
)
|
||||
.children(if extra_count > 0 {
|
||||
Some(
|
||||
div()
|
||||
.ml_1()
|
||||
.child(Label::new(format!("+{extra_count}")))
|
||||
.into_any_element(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
});
|
||||
|
||||
Some(pile)
|
||||
Some(Avatar::new(follower.avatar_uri.clone()))
|
||||
},
|
||||
))
|
||||
.children(if extra_count > 0 {
|
||||
Some(
|
||||
div()
|
||||
.ml_1()
|
||||
.child(Label::new(format!("+{extra_count}")))
|
||||
.into_any_element(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.render(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use gpui::{div, AnyElement, ParentElement, RenderOnce, Styled, WindowContext};
|
||||
use gpui::{div, AnyElement, Div, ParentElement, Styled};
|
||||
use smallvec::SmallVec;
|
||||
use ui::FluentBuilder;
|
||||
|
||||
#[derive(Default, gpui::IntoElement)]
|
||||
#[derive(Default)]
|
||||
pub struct FacePile {
|
||||
pub faces: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl RenderOnce for FacePile {
|
||||
fn render(self, _: &mut WindowContext) -> impl gpui::IntoElement {
|
||||
impl FacePile {
|
||||
pub fn render(self) -> Div {
|
||||
let player_count = self.faces.len();
|
||||
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
|
||||
let isnt_last = ix < player_count - 1;
|
||||
|
@ -18,7 +18,7 @@ impl RenderOnce for FacePile {
|
|||
.when(isnt_last, |div| div.neg_mr_1())
|
||||
.child(player)
|
||||
});
|
||||
div().p_1().flex().items_center().children(player_list)
|
||||
div().flex().items_center().children(player_list)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -321,10 +321,7 @@ impl Element for AnyView {
|
|||
}
|
||||
}
|
||||
|
||||
let mut element = state
|
||||
.element
|
||||
.take()
|
||||
.unwrap_or_else(|| (self.request_layout)(self, cx).1);
|
||||
let mut element = (self.request_layout)(self, cx).1;
|
||||
element.draw(bounds.origin, bounds.size.into(), cx);
|
||||
|
||||
state.cache_key = Some(ViewCacheKey {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use gpui::ClickEvent;
|
||||
|
||||
use crate::{prelude::*, Color, IconButton, IconName, IconSize};
|
||||
|
@ -6,7 +8,7 @@ use crate::{prelude::*, Color, IconButton, IconName, IconSize};
|
|||
pub struct Disclosure {
|
||||
id: ElementId,
|
||||
is_open: bool,
|
||||
on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
}
|
||||
|
||||
impl Disclosure {
|
||||
|
@ -20,7 +22,7 @@ impl Disclosure {
|
|||
|
||||
pub fn on_toggle(
|
||||
mut self,
|
||||
handler: impl Into<Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
|
||||
handler: impl Into<Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>>,
|
||||
) -> Self {
|
||||
self.on_toggle = handler.into();
|
||||
self
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{h_flex, prelude::*, Disclosure, Label};
|
||||
use gpui::{AnyElement, ClickEvent};
|
||||
|
||||
|
@ -14,7 +16,7 @@ pub struct ListHeader {
|
|||
/// It will obscure the `end_slot` when visible.
|
||||
end_hover_slot: Option<AnyElement>,
|
||||
toggle: Option<bool>,
|
||||
on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
inset: bool,
|
||||
selected: bool,
|
||||
}
|
||||
|
@ -42,7 +44,7 @@ impl ListHeader {
|
|||
mut self,
|
||||
on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.on_toggle = Some(Box::new(on_toggle));
|
||||
self.on_toggle = Some(Arc::new(on_toggle));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -98,15 +100,19 @@ impl RenderOnce for ListHeader {
|
|||
h_flex()
|
||||
.gap_1()
|
||||
.children(self.toggle.map(|is_open| {
|
||||
Disclosure::new("toggle", is_open).on_toggle(self.on_toggle)
|
||||
Disclosure::new("toggle", is_open).on_toggle(self.on_toggle.clone())
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.id("label_container")
|
||||
.flex()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.children(self.start_slot)
|
||||
.child(Label::new(self.label.clone()).color(Color::Muted)),
|
||||
.child(Label::new(self.label.clone()).color(Color::Muted))
|
||||
.when_some(self.on_toggle, |this, on_toggle| {
|
||||
this.on_click(move |event, cx| on_toggle(event, cx))
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(h_flex().children(self.end_slot))
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use gpui::{px, AnyElement, AnyView, ClickEvent, MouseButton, MouseDownEvent, Pixels};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
@ -29,7 +31,7 @@ pub struct ListItem {
|
|||
toggle: Option<bool>,
|
||||
inset: bool,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
on_toggle: Option<Arc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
|
||||
on_secondary_mouse_down: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
|
@ -104,7 +106,7 @@ impl ListItem {
|
|||
mut self,
|
||||
on_toggle: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.on_toggle = Some(Box::new(on_toggle));
|
||||
self.on_toggle = Some(Arc::new(on_toggle));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ lazy_static::lazy_static! {
|
|||
pub static ref COPILOT_DIR: PathBuf = HOME.join("Library/Application Support/Zed/copilot");
|
||||
pub static ref DEFAULT_PRETTIER_DIR: PathBuf = HOME.join("Library/Application Support/Zed/prettier");
|
||||
pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db");
|
||||
pub static ref CRASHES_DIR: PathBuf = HOME.join("Library/Logs/DiagnosticReports");
|
||||
pub static ref CRASHES_RETIRED_DIR: PathBuf = HOME.join("Library/Logs/DiagnosticReports/Retired");
|
||||
pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json");
|
||||
pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json");
|
||||
pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt");
|
||||
|
|
|
@ -43,7 +43,8 @@ use util::{
|
|||
async_maybe,
|
||||
channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL},
|
||||
http::{self, HttpClient},
|
||||
paths, ResultExt,
|
||||
paths::{self, CRASHES_DIR, CRASHES_RETIRED_DIR},
|
||||
ResultExt,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use welcome::{show_welcome_view, BaseKeymap, FIRST_OPEN};
|
||||
|
@ -229,14 +230,14 @@ fn main() {
|
|||
initialize_workspace(app_state.clone(), cx);
|
||||
|
||||
if stdout_is_a_pty() {
|
||||
upload_panics_and_crashes(http.clone(), cx);
|
||||
cx.activate(true);
|
||||
let urls = collect_url_args();
|
||||
if !urls.is_empty() {
|
||||
listener.open_urls(&urls)
|
||||
}
|
||||
} else {
|
||||
upload_previous_panics(http.clone(), cx);
|
||||
|
||||
upload_panics_and_crashes(http.clone(), cx);
|
||||
// TODO Development mode that forces the CLI mode usually runs Zed binary as is instead
|
||||
// of an *app, hence gets no specific callbacks run. Emulate them here, if needed.
|
||||
if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some()
|
||||
|
@ -543,7 +544,22 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
|
|||
let mut backtrace = backtrace
|
||||
.frames()
|
||||
.iter()
|
||||
.filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?)))
|
||||
.flat_map(|frame| {
|
||||
frame.symbols().iter().filter_map(|symbol| {
|
||||
let name = symbol.name()?;
|
||||
let addr = symbol.addr()? as usize;
|
||||
let position = if let (Some(path), Some(lineno)) = (
|
||||
symbol.filename().and_then(|path| path.file_name()),
|
||||
symbol.lineno(),
|
||||
) {
|
||||
format!("{}:{}", path.to_string_lossy(), lineno)
|
||||
} else {
|
||||
"?".to_string()
|
||||
};
|
||||
|
||||
Some(format!("{:} ({:#x}) at {}", name, addr, position))
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Strip out leading stack frames for rust panic-handling.
|
||||
|
@ -599,77 +615,154 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
|
|||
}));
|
||||
}
|
||||
|
||||
fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
|
||||
fn upload_panics_and_crashes(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
|
||||
let telemetry_settings = *client::TelemetrySettings::get_global(cx);
|
||||
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);
|
||||
let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
|
||||
while let Some(child) = children.next().await {
|
||||
let child = child?;
|
||||
let child_path = child.path();
|
||||
|
||||
if child_path.extension() != Some(OsStr::new("panic")) {
|
||||
continue;
|
||||
}
|
||||
let filename = if let Some(filename) = child_path.file_name() {
|
||||
filename.to_string_lossy()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !filename.starts_with("zed") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if telemetry_settings.diagnostics {
|
||||
let panic_file_content = smol::fs::read_to_string(&child_path)
|
||||
.await
|
||||
.context("error reading panic file")?;
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
if let Some(panic) = panic {
|
||||
let body = serde_json::to_string(&PanicRequest {
|
||||
panic,
|
||||
token: client::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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We've done what we can, delete the file
|
||||
std::fs::remove_file(child_path)
|
||||
.context("error removing panic")
|
||||
.log_err();
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
upload_previous_panics(http.clone(), telemetry_settings)
|
||||
.await
|
||||
.log_err();
|
||||
upload_previous_crashes(http, telemetry_settings)
|
||||
.await
|
||||
.log_err()
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
.detach()
|
||||
}
|
||||
|
||||
/// upload panics to us (via zed.dev)
|
||||
async fn upload_previous_panics(
|
||||
http: Arc<dyn HttpClient>,
|
||||
telemetry_settings: client::TelemetrySettings,
|
||||
) -> Result<()> {
|
||||
let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);
|
||||
let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
|
||||
while let Some(child) = children.next().await {
|
||||
let child = child?;
|
||||
let child_path = child.path();
|
||||
|
||||
if child_path.extension() != Some(OsStr::new("panic")) {
|
||||
continue;
|
||||
}
|
||||
let filename = if let Some(filename) = child_path.file_name() {
|
||||
filename.to_string_lossy()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !filename.starts_with("zed") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if telemetry_settings.diagnostics {
|
||||
let panic_file_content = smol::fs::read_to_string(&child_path)
|
||||
.await
|
||||
.context("error reading panic file")?;
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
if let Some(panic) = panic {
|
||||
let body = serde_json::to_string(&PanicRequest {
|
||||
panic,
|
||||
token: client::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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We've done what we can, delete the file
|
||||
std::fs::remove_file(child_path)
|
||||
.context("error removing panic")
|
||||
.log_err();
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
|
||||
static LAST_CRASH_UPLOADED: &'static str = "LAST_CRASH_UPLOADED";
|
||||
|
||||
/// upload crashes from apple's diagnostic reports to our server.
|
||||
/// (only if telemetry is enabled)
|
||||
async fn upload_previous_crashes(
|
||||
http: Arc<dyn HttpClient>,
|
||||
telemetry_settings: client::TelemetrySettings,
|
||||
) -> Result<()> {
|
||||
if !telemetry_settings.diagnostics {
|
||||
return Ok(());
|
||||
}
|
||||
let last_uploaded = KEY_VALUE_STORE
|
||||
.read_kvp(LAST_CRASH_UPLOADED)?
|
||||
.unwrap_or("zed-2024-01-17-221900.ips".to_string()); // don't upload old crash reports from before we had this.
|
||||
let mut uploaded = last_uploaded.clone();
|
||||
|
||||
let crash_report_url = format!("{}/api/crash", &*client::ZED_SERVER_URL);
|
||||
|
||||
for dir in [&*CRASHES_DIR, &*CRASHES_RETIRED_DIR] {
|
||||
let mut children = smol::fs::read_dir(&dir).await?;
|
||||
while let Some(child) = children.next().await {
|
||||
let child = child?;
|
||||
let Some(filename) = child
|
||||
.path()
|
||||
.file_name()
|
||||
.map(|f| f.to_string_lossy().to_lowercase())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !filename.starts_with("zed-") || !filename.ends_with(".ips") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if filename <= last_uploaded {
|
||||
continue;
|
||||
}
|
||||
|
||||
let body = smol::fs::read_to_string(&child.path())
|
||||
.await
|
||||
.context("error reading crash file")?;
|
||||
|
||||
let request = Request::post(&crash_report_url)
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.header("Content-Type", "text/plain")
|
||||
.header(
|
||||
"Authorization",
|
||||
format!("token {}", client::ZED_SECRET_CLIENT_TOKEN),
|
||||
)
|
||||
.body(body.into())?;
|
||||
|
||||
let response = http.send(request).await.context("error sending crash")?;
|
||||
if !response.status().is_success() {
|
||||
log::error!("Error uploading crash to server: {}", response.status());
|
||||
}
|
||||
|
||||
if uploaded < filename {
|
||||
uploaded = filename.clone();
|
||||
KEY_VALUE_STORE
|
||||
.write_kvp(LAST_CRASH_UPLOADED.to_string(), filename)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn load_login_shell_environment() -> Result<()> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue