Compare commits

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

9 commits

Author SHA1 Message Date
Max Brunsfeld
99b776b593 Fix possibility of infinite loop in selections_with_autoclose_regions (#3138)
Previously, that method could loop forever if the editor's autoclose
regions had unexpected selection ids.

Something must have changed recently that allowed this invariant to be
violated, but regardless, this code should not have relied on that
invariant to terminate like this.
2023-10-18 14:36:02 -04:00
Joseph T. Lyons
25d1939dcc v0.108.x stable 2023-10-18 12:30:57 -04:00
Kirill Bulatov
e45a7735e9 Cap every language server logs (#3134)
* on opening a language server's logs, a new editor for server logs is
now created from `\n`-joined `VecDeque` elements instead of a buffer, as
before
* every `VecDeque` entry is a log line we receiver out of stderr or LSP
server, and their general amount is capped with `let
MAX_STORED_LOG_ENTRIES: usize = 2000;`
* currently opened editor with logs (`Editor::multi_line`) keeps getting
log lines appended and may get over this cap, but only last stored 2000
entries will be restored on reopen
* similarly, cap rpc message logs

Release Notes:

- Improved memory usage by storing less language LSP server and rpc logs
2023-10-18 09:26:14 -07:00
Joseph T. Lyons
10ed22a17d zed 0.108.2 2023-10-16 14:38:41 -04:00
Joseph T. Lyons
4deedad94e Fix telemetry-related crash on start up (#3131)
Fixes (hopefully)
[#2136](https://github.com/zed-industries/community/issues/2136).

Release Notes:

- N/A
2023-10-16 14:11:20 -04:00
Max Brunsfeld
54a44263ea zed 0.108.1 2023-10-13 09:55:10 -07:00
Max Brunsfeld
251cd22711 Fix panic when following due to disconnected channel notes views (#3124)
In addition to fixing a panic, this makes it slightly more convenient to
re-open disconnected channel notes views. I didn't make it automatic,
but it will at least replace the previous, disconnected view.

Release Notes:

- Fixed a crash that sometimes occurred when following someone with a
disconnected channel notes view open.
2023-10-13 09:53:33 -07:00
Kirill Bulatov
e63d86e0b9 Update diagnostics indicator when diagnostics are udpated (#3128)
Release Notes:

- Fixed diagnostics indicator not showing proper diagnostics count
2023-10-13 12:31:16 +03:00
Joseph T. Lyons
59928b6ee0 v0.108.x preview 2023-10-11 12:40:23 -04:00
11 changed files with 238 additions and 121 deletions

2
Cargo.lock generated
View file

@ -10081,7 +10081,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.108.0"
version = "0.108.2"
dependencies = [
"activity_indicator",
"anyhow",

View file

@ -99,6 +99,10 @@ impl ChannelBuffer {
}))
}
pub fn remote_id(&self, cx: &AppContext) -> u64 {
self.buffer.read(cx).remote_id()
}
pub fn user_store(&self) -> &ModelHandle<UserStore> {
&self.user_store
}

View file

@ -114,12 +114,21 @@ impl ChannelStore {
let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
while let Some(status) = connection_status.next().await {
let this = this.upgrade(&cx)?;
match status {
client::Status::Connected { .. } => {
this.update(&mut cx, |this, cx| this.handle_connect(cx))
.await
.log_err()?;
}
client::Status::SignedOut | client::Status::UpgradeRequired => {
this.update(&mut cx, |this, cx| this.handle_disconnect(false, cx));
}
_ => {
this.update(&mut cx, |this, cx| this.handle_disconnect(true, cx));
}
}
if status.is_connected() {
this.update(&mut cx, |this, cx| this.handle_connect(cx))
.await
.log_err()?;
} else {
this.update(&mut cx, |this, cx| this.handle_disconnect(cx));
}
}
Some(())
@ -823,7 +832,7 @@ impl ChannelStore {
})
}
fn handle_disconnect(&mut self, cx: &mut ModelContext<Self>) {
fn handle_disconnect(&mut self, wait_for_reconnect: bool, cx: &mut ModelContext<Self>) {
self.channel_index.clear();
self.channel_invitations.clear();
self.channel_participants.clear();
@ -834,7 +843,10 @@ impl ChannelStore {
self.disconnect_channel_buffers_task.get_or_insert_with(|| {
cx.spawn_weak(|this, mut cx| async move {
cx.background().timer(RECONNECT_TIMEOUT).await;
if wait_for_reconnect {
cx.background().timer(RECONNECT_TIMEOUT).await;
}
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
for (_, buffer) in this.opened_buffers.drain() {

View file

@ -4,7 +4,9 @@ use lazy_static::lazy_static;
use parking_lot::Mutex;
use serde::Serialize;
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
use sysinfo::{
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
};
use tempfile::NamedTempFile;
use util::http::HttpClient;
use util::{channel::ReleaseChannel, TryFutureExt};
@ -166,8 +168,16 @@ impl Telemetry {
let this = self.clone();
cx.spawn(|mut cx| async move {
let mut system = System::new_all();
system.refresh_all();
// Avoiding calling `System::new_all()`, as there have been crashes related to it
let refresh_kind = RefreshKind::new()
.with_memory() // For memory usage
.with_processes(ProcessRefreshKind::everything()) // For process usage
.with_cpu(CpuRefreshKind::everything()); // For core count
let mut system = System::new_with_specifics(refresh_kind);
// Avoiding calling `refresh_all()`, just update what we need
system.refresh_specifics(refresh_kind);
loop {
// Waiting some amount of time before the first query is important to get a reasonable value
@ -175,8 +185,7 @@ impl Telemetry {
const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(60);
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
system.refresh_memory();
system.refresh_processes();
system.refresh_specifics(refresh_kind);
let current_process = Pid::from_u32(std::process::id());
let Some(process) = system.processes().get(&current_process) else {

View file

@ -24,7 +24,7 @@ use workspace::{
item::{FollowableItem, Item, ItemHandle},
register_followable_item,
searchable::SearchableItemHandle,
ItemNavHistory, Pane, ViewId, Workspace, WorkspaceId,
ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
};
actions!(channel_view, [Deploy]);
@ -93,15 +93,36 @@ impl ChannelView {
}
pane.update(&mut cx, |pane, cx| {
pane.items_of_type::<Self>()
.find(|channel_view| channel_view.read(cx).channel_buffer == channel_buffer)
.unwrap_or_else(|| {
cx.add_view(|cx| {
let mut this = Self::new(project, channel_store, channel_buffer, cx);
this.acknowledge_buffer_version(cx);
this
})
})
let buffer_id = channel_buffer.read(cx).remote_id(cx);
let existing_view = pane
.items_of_type::<Self>()
.find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
// If this channel buffer is already open in this pane, just return it.
if let Some(existing_view) = existing_view.clone() {
if existing_view.read(cx).channel_buffer == channel_buffer {
return existing_view;
}
}
let view = cx.add_view(|cx| {
let mut this = Self::new(project, channel_store, channel_buffer, cx);
this.acknowledge_buffer_version(cx);
this
});
// If the pane contained a disconnected view for this channel buffer,
// replace that.
if let Some(existing_item) = existing_view {
if let Some(ix) = pane.index_for_item(&existing_item) {
pane.close_item_by_id(existing_item.id(), SaveIntent::Skip, cx)
.detach();
pane.add_item(Box::new(view.clone()), true, true, Some(ix), cx);
}
}
view
})
.ok_or_else(|| anyhow!("pane was dropped"))
})
@ -285,10 +306,14 @@ impl FollowableItem for ChannelView {
}
fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
let channel = self.channel_buffer.read(cx).channel();
let channel_buffer = self.channel_buffer.read(cx);
if !channel_buffer.is_connected() {
return None;
}
Some(proto::view::Variant::ChannelView(
proto::view::ChannelView {
channel_id: channel.id,
channel_id: channel_buffer.channel().id,
editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(cx)
{

View file

@ -38,6 +38,10 @@ impl DiagnosticIndicator {
this.in_progress_checks.remove(language_server_id);
cx.notify();
}
project::Event::DiagnosticsUpdated { .. } => {
this.summary = project.read(cx).diagnostic_summary(cx);
cx.notify();
}
_ => {}
})
.detach();

View file

@ -2877,8 +2877,10 @@ impl Editor {
i = 0;
} else if pair_state.range.start.to_offset(buffer) > range.end {
break;
} else if pair_state.selection_id == selection.id {
enclosing = Some(pair_state);
} else {
if pair_state.selection_id == selection.id {
enclosing = Some(pair_state);
}
i += 1;
}
}

View file

@ -1,5 +1,5 @@
use collections::HashMap;
use editor::Editor;
use collections::{HashMap, VecDeque};
use editor::{Editor, MoveToEnd};
use futures::{channel::mpsc, StreamExt};
use gpui::{
actions,
@ -11,7 +11,7 @@ use gpui::{
AnyElement, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription, View,
ViewContext, ViewHandle, WeakModelHandle,
};
use language::{Buffer, LanguageServerId, LanguageServerName};
use language::{LanguageServerId, LanguageServerName};
use lsp::IoKind;
use project::{search::SearchQuery, Project};
use std::{borrow::Cow, sync::Arc};
@ -22,8 +22,9 @@ use workspace::{
ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceCreated,
};
const SEND_LINE: &str = "// Send:\n";
const RECEIVE_LINE: &str = "// Receive:\n";
const SEND_LINE: &str = "// Send:";
const RECEIVE_LINE: &str = "// Receive:";
const MAX_STORED_LOG_ENTRIES: usize = 2000;
pub struct LogStore {
projects: HashMap<WeakModelHandle<Project>, ProjectState>,
@ -36,24 +37,25 @@ struct ProjectState {
}
struct LanguageServerState {
log_buffer: ModelHandle<Buffer>,
log_messages: VecDeque<String>,
rpc_state: Option<LanguageServerRpcState>,
_io_logs_subscription: Option<lsp::Subscription>,
_lsp_logs_subscription: Option<lsp::Subscription>,
}
struct LanguageServerRpcState {
buffer: ModelHandle<Buffer>,
rpc_messages: VecDeque<String>,
last_message_kind: Option<MessageKind>,
}
pub struct LspLogView {
pub(crate) editor: ViewHandle<Editor>,
editor_subscription: Subscription,
log_store: ModelHandle<LogStore>,
current_server_id: Option<LanguageServerId>,
is_showing_rpc_trace: bool,
project: ModelHandle<Project>,
_log_store_subscription: Subscription,
_log_store_subscriptions: Vec<Subscription>,
}
pub struct LspLogToolbarItemView {
@ -122,10 +124,9 @@ impl LogStore {
io_tx,
};
cx.spawn_weak(|this, mut cx| async move {
while let Some((project, server_id, io_kind, mut message)) = io_rx.next().await {
while let Some((project, server_id, io_kind, message)) = io_rx.next().await {
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| {
message.push('\n');
this.on_io(project, server_id, io_kind, &message, cx);
});
}
@ -168,15 +169,13 @@ impl LogStore {
project: &ModelHandle<Project>,
id: LanguageServerId,
cx: &mut ModelContext<Self>,
) -> Option<ModelHandle<Buffer>> {
) -> Option<&mut LanguageServerState> {
let project_state = self.projects.get_mut(&project.downgrade())?;
let server_state = project_state.servers.entry(id).or_insert_with(|| {
cx.notify();
LanguageServerState {
rpc_state: None,
log_buffer: cx
.add_model(|cx| Buffer::new(0, cx.model_id() as u64, ""))
.clone(),
log_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
_io_logs_subscription: None,
_lsp_logs_subscription: None,
}
@ -186,7 +185,7 @@ impl LogStore {
if let Some(server) = server.as_deref() {
if server.has_notification_handler::<lsp::notification::LogMessage>() {
// Another event wants to re-add the server that was already added and subscribed to, avoid doing it again.
return Some(server_state.log_buffer.clone());
return Some(server_state);
}
}
@ -215,7 +214,7 @@ impl LogStore {
}
})
});
Some(server_state.log_buffer.clone())
Some(server_state)
}
fn add_language_server_log(
@ -225,24 +224,26 @@ impl LogStore {
message: &str,
cx: &mut ModelContext<Self>,
) -> Option<()> {
let buffer = match self
let language_server_state = match self
.projects
.get_mut(&project.downgrade())?
.servers
.get(&id)
.map(|state| state.log_buffer.clone())
.get_mut(&id)
{
Some(existing_buffer) => existing_buffer,
Some(existing_state) => existing_state,
None => self.add_language_server(&project, id, cx)?,
};
buffer.update(cx, |buffer, cx| {
let len = buffer.len();
let has_newline = message.ends_with("\n");
buffer.edit([(len..len, message)], None, cx);
if !has_newline {
let len = buffer.len();
buffer.edit([(len..len, "\n")], None, cx);
}
let log_lines = &mut language_server_state.log_messages;
while log_lines.len() >= MAX_STORED_LOG_ENTRIES {
log_lines.pop_front();
}
let message = message.trim();
log_lines.push_back(message.to_string());
cx.emit(Event::NewServerLogEntry {
id,
entry: message.to_string(),
is_rpc: false,
});
cx.notify();
Some(())
@ -260,46 +261,32 @@ impl LogStore {
Some(())
}
pub fn log_buffer_for_server(
fn server_logs(
&self,
project: &ModelHandle<Project>,
server_id: LanguageServerId,
) -> Option<ModelHandle<Buffer>> {
) -> Option<&VecDeque<String>> {
let weak_project = project.downgrade();
let project_state = self.projects.get(&weak_project)?;
let server_state = project_state.servers.get(&server_id)?;
Some(server_state.log_buffer.clone())
Some(&server_state.log_messages)
}
fn enable_rpc_trace_for_language_server(
&mut self,
project: &ModelHandle<Project>,
server_id: LanguageServerId,
cx: &mut ModelContext<Self>,
) -> Option<ModelHandle<Buffer>> {
) -> Option<&mut LanguageServerRpcState> {
let weak_project = project.downgrade();
let project_state = self.projects.get_mut(&weak_project)?;
let server_state = project_state.servers.get_mut(&server_id)?;
let rpc_state = server_state.rpc_state.get_or_insert_with(|| {
let language = project.read(cx).languages().language_for_name("JSON");
let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, ""));
cx.spawn_weak({
let buffer = buffer.clone();
|_, mut cx| async move {
let language = language.await.ok();
buffer.update(&mut cx, |buffer, cx| {
buffer.set_language(language, cx);
});
}
})
.detach();
LanguageServerRpcState {
buffer,
let rpc_state = server_state
.rpc_state
.get_or_insert_with(|| LanguageServerRpcState {
rpc_messages: VecDeque::with_capacity(MAX_STORED_LOG_ENTRIES),
last_message_kind: None,
}
});
Some(rpc_state.buffer.clone())
});
Some(rpc_state)
}
pub fn disable_rpc_trace_for_language_server(
@ -328,7 +315,7 @@ impl LogStore {
IoKind::StdIn => false,
IoKind::StdErr => {
let project = project.upgrade(cx)?;
let message = format!("stderr: {}\n", message.trim());
let message = format!("stderr: {}", message.trim());
self.add_language_server_log(&project, language_server_id, &message, cx);
return Some(());
}
@ -341,24 +328,37 @@ impl LogStore {
.get_mut(&language_server_id)?
.rpc_state
.as_mut()?;
state.buffer.update(cx, |buffer, cx| {
let kind = if is_received {
MessageKind::Receive
} else {
MessageKind::Send
let kind = if is_received {
MessageKind::Receive
} else {
MessageKind::Send
};
let rpc_log_lines = &mut state.rpc_messages;
if state.last_message_kind != Some(kind) {
let line_before_message = match kind {
MessageKind::Send => SEND_LINE,
MessageKind::Receive => RECEIVE_LINE,
};
if state.last_message_kind != Some(kind) {
let len = buffer.len();
let line = match kind {
MessageKind::Send => SEND_LINE,
MessageKind::Receive => RECEIVE_LINE,
};
buffer.edit([(len..len, line)], None, cx);
state.last_message_kind = Some(kind);
}
let len = buffer.len();
buffer.edit([(len..len, message)], None, cx);
rpc_log_lines.push_back(line_before_message.to_string());
cx.emit(Event::NewServerLogEntry {
id: language_server_id,
entry: line_before_message.to_string(),
is_rpc: true,
});
}
while rpc_log_lines.len() >= MAX_STORED_LOG_ENTRIES {
rpc_log_lines.pop_front();
}
let message = message.trim();
rpc_log_lines.push_back(message.to_string());
cx.emit(Event::NewServerLogEntry {
id: language_server_id,
entry: message.to_string(),
is_rpc: true,
});
cx.notify();
Some(())
}
}
@ -374,8 +374,7 @@ impl LspLogView {
.projects
.get(&project.downgrade())
.and_then(|project| project.servers.keys().copied().next());
let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, ""));
let _log_store_subscription = cx.observe(&log_store, |this, store, cx| {
let model_changes_subscription = cx.observe(&log_store, |this, store, cx| {
(|| -> Option<()> {
let project_state = store.read(cx).projects.get(&this.project.downgrade())?;
if let Some(current_lsp) = this.current_server_id {
@ -411,13 +410,31 @@ impl LspLogView {
cx.notify();
});
let events_subscriptions = cx.subscribe(&log_store, |log_view, _, e, cx| match e {
Event::NewServerLogEntry { id, entry, is_rpc } => {
if log_view.current_server_id == Some(*id) {
if (*is_rpc && log_view.is_showing_rpc_trace)
|| (!*is_rpc && !log_view.is_showing_rpc_trace)
{
log_view.editor.update(cx, |editor, cx| {
editor.set_read_only(false);
editor.handle_input(entry.trim(), cx);
editor.handle_input("\n", cx);
editor.set_read_only(true);
});
}
}
}
});
let (editor, editor_subscription) = Self::editor_for_logs(String::new(), cx);
let mut this = Self {
editor: Self::editor_for_buffer(project.clone(), buffer, cx),
editor,
editor_subscription,
project,
log_store,
current_server_id: None,
is_showing_rpc_trace: false,
_log_store_subscription,
_log_store_subscriptions: vec![model_changes_subscription, events_subscriptions],
};
if let Some(server_id) = server_id {
this.show_logs_for_server(server_id, cx);
@ -425,20 +442,19 @@ impl LspLogView {
this
}
fn editor_for_buffer(
project: ModelHandle<Project>,
buffer: ModelHandle<Buffer>,
fn editor_for_logs(
log_contents: String,
cx: &mut ViewContext<Self>,
) -> ViewHandle<Editor> {
) -> (ViewHandle<Editor>, Subscription) {
let editor = cx.add_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
let mut editor = Editor::multi_line(None, cx);
editor.set_text(log_contents, cx);
editor.move_to_end(&MoveToEnd, cx);
editor.set_read_only(true);
editor.move_to_end(&Default::default(), cx);
editor
});
cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()))
.detach();
editor
let editor_subscription = cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()));
(editor, editor_subscription)
}
pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
@ -487,14 +503,17 @@ impl LspLogView {
}
fn show_logs_for_server(&mut self, server_id: LanguageServerId, cx: &mut ViewContext<Self>) {
let buffer = self
let log_contents = self
.log_store
.read(cx)
.log_buffer_for_server(&self.project, server_id);
if let Some(buffer) = buffer {
.server_logs(&self.project, server_id)
.map(log_contents);
if let Some(log_contents) = log_contents {
self.current_server_id = Some(server_id);
self.is_showing_rpc_trace = false;
self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx);
let (editor, editor_subscription) = Self::editor_for_logs(log_contents, cx);
self.editor = editor;
self.editor_subscription = editor_subscription;
cx.notify();
}
}
@ -504,13 +523,37 @@ impl LspLogView {
server_id: LanguageServerId,
cx: &mut ViewContext<Self>,
) {
let buffer = self.log_store.update(cx, |log_set, cx| {
log_set.enable_rpc_trace_for_language_server(&self.project, server_id, cx)
let rpc_log = self.log_store.update(cx, |log_store, _| {
log_store
.enable_rpc_trace_for_language_server(&self.project, server_id)
.map(|state| log_contents(&state.rpc_messages))
});
if let Some(buffer) = buffer {
if let Some(rpc_log) = rpc_log {
self.current_server_id = Some(server_id);
self.is_showing_rpc_trace = true;
self.editor = Self::editor_for_buffer(self.project.clone(), buffer, cx);
let (editor, editor_subscription) = Self::editor_for_logs(rpc_log, cx);
let language = self.project.read(cx).languages().language_for_name("JSON");
editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.expect("log buffer should be a singleton")
.update(cx, |_, cx| {
cx.spawn_weak({
let buffer = cx.handle();
|_, mut cx| async move {
let language = language.await.ok();
buffer.update(&mut cx, |buffer, cx| {
buffer.set_language(language, cx);
});
}
})
.detach();
});
self.editor = editor;
self.editor_subscription = editor_subscription;
cx.notify();
}
}
@ -523,7 +566,7 @@ impl LspLogView {
) {
self.log_store.update(cx, |log_store, cx| {
if enabled {
log_store.enable_rpc_trace_for_language_server(&self.project, server_id, cx);
log_store.enable_rpc_trace_for_language_server(&self.project, server_id);
} else {
log_store.disable_rpc_trace_for_language_server(&self.project, server_id, cx);
}
@ -535,6 +578,16 @@ impl LspLogView {
}
}
fn log_contents(lines: &VecDeque<String>) -> String {
let (a, b) = lines.as_slices();
let log_contents = a.join("\n");
if b.is_empty() {
log_contents
} else {
log_contents + "\n" + &b.join("\n")
}
}
impl View for LspLogView {
fn ui_name() -> &'static str {
"LspLogView"
@ -947,8 +1000,16 @@ impl LspLogToolbarItemView {
}
}
pub enum Event {
NewServerLogEntry {
id: LanguageServerId,
entry: String,
is_rpc: bool,
},
}
impl Entity for LogStore {
type Event = ();
type Event = Event;
}
impl Entity for LspLogView {

View file

@ -2867,8 +2867,8 @@ impl Project {
move |mut params, mut cx| {
let this = this;
let adapter = adapter.clone();
adapter.process_diagnostics(&mut params);
if let Some(this) = this.upgrade(&cx) {
adapter.process_diagnostics(&mut params);
this.update(&mut cx, |this, cx| {
this.update_diagnostics(
server_id,

View file

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

View file

@ -1 +1 @@
dev
stable