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]] [[package]]
name = "zed" name = "zed"
version = "0.108.0" version = "0.108.2"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"anyhow", "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> { pub fn user_store(&self) -> &ModelHandle<UserStore> {
&self.user_store &self.user_store
} }

View file

@ -114,12 +114,21 @@ impl ChannelStore {
let watch_connection_status = cx.spawn_weak(|this, mut cx| async move { let watch_connection_status = cx.spawn_weak(|this, mut cx| async move {
while let Some(status) = connection_status.next().await { while let Some(status) = connection_status.next().await {
let this = this.upgrade(&cx)?; 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() { if status.is_connected() {
this.update(&mut cx, |this, cx| this.handle_connect(cx))
.await
.log_err()?;
} else { } else {
this.update(&mut cx, |this, cx| this.handle_disconnect(cx));
} }
} }
Some(()) 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_index.clear();
self.channel_invitations.clear(); self.channel_invitations.clear();
self.channel_participants.clear(); self.channel_participants.clear();
@ -834,7 +843,10 @@ impl ChannelStore {
self.disconnect_channel_buffers_task.get_or_insert_with(|| { self.disconnect_channel_buffers_task.get_or_insert_with(|| {
cx.spawn_weak(|this, mut cx| async move { 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) { if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
for (_, buffer) in this.opened_buffers.drain() { for (_, buffer) in this.opened_buffers.drain() {

View file

@ -4,7 +4,9 @@ use lazy_static::lazy_static;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Serialize; use serde::Serialize;
use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration}; 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 tempfile::NamedTempFile;
use util::http::HttpClient; use util::http::HttpClient;
use util::{channel::ReleaseChannel, TryFutureExt}; use util::{channel::ReleaseChannel, TryFutureExt};
@ -166,8 +168,16 @@ impl Telemetry {
let this = self.clone(); let this = self.clone();
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
let mut system = System::new_all(); // Avoiding calling `System::new_all()`, as there have been crashes related to it
system.refresh_all(); 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 { loop {
// Waiting some amount of time before the first query is important to get a reasonable value // 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); const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(60);
smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await; smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
system.refresh_memory(); system.refresh_specifics(refresh_kind);
system.refresh_processes();
let current_process = Pid::from_u32(std::process::id()); let current_process = Pid::from_u32(std::process::id());
let Some(process) = system.processes().get(&current_process) else { let Some(process) = system.processes().get(&current_process) else {

View file

@ -24,7 +24,7 @@ use workspace::{
item::{FollowableItem, Item, ItemHandle}, item::{FollowableItem, Item, ItemHandle},
register_followable_item, register_followable_item,
searchable::SearchableItemHandle, searchable::SearchableItemHandle,
ItemNavHistory, Pane, ViewId, Workspace, WorkspaceId, ItemNavHistory, Pane, SaveIntent, ViewId, Workspace, WorkspaceId,
}; };
actions!(channel_view, [Deploy]); actions!(channel_view, [Deploy]);
@ -93,15 +93,36 @@ impl ChannelView {
} }
pane.update(&mut cx, |pane, cx| { pane.update(&mut cx, |pane, cx| {
pane.items_of_type::<Self>() let buffer_id = channel_buffer.read(cx).remote_id(cx);
.find(|channel_view| channel_view.read(cx).channel_buffer == channel_buffer)
.unwrap_or_else(|| { let existing_view = pane
cx.add_view(|cx| { .items_of_type::<Self>()
let mut this = Self::new(project, channel_store, channel_buffer, cx); .find(|view| view.read(cx).channel_buffer.read(cx).remote_id(cx) == buffer_id);
this.acknowledge_buffer_version(cx);
this // 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")) .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> { 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( Some(proto::view::Variant::ChannelView(
proto::view::ChannelView { proto::view::ChannelView {
channel_id: channel.id, channel_id: channel_buffer.channel().id,
editor: if let Some(proto::view::Variant::Editor(proto)) = editor: if let Some(proto::view::Variant::Editor(proto)) =
self.editor.read(cx).to_state_proto(cx) self.editor.read(cx).to_state_proto(cx)
{ {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1 +1 @@
dev stable