Merge branch 'main' into fix-popin-for-project-panel

This commit is contained in:
Mikayla Maki 2024-01-18 15:48:57 -08:00 committed by Mikayla
commit 20b58fea35
No known key found for this signature in database
72 changed files with 435 additions and 37480 deletions

View file

@ -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(

View file

@ -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 {}",

View file

@ -1 +1,8 @@
["nathansobo", "as-cii", "maxbrunsfeld", "iamnbutler"]
[
"nathansobo",
"as-cii",
"maxbrunsfeld",
"iamnbutler",
"mikayla-maki",
"JosephTLyons"
]

View file

@ -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(

View file

@ -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,
&current_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,
&current_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>) {

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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))

View file

@ -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
}

View file

@ -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");

View file

@ -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<()> {